Im ersten Teil haben wir die wichtigsten Konzepte kennengelernt. Es
wurde ein wenig theoretischer und wir haben viel über R kennengelernt.
Ich möchte Teil 1 ein wenig mit der Grundgrammatik vergleichen.
In Teil 2 geht es um Data Science. Hier lernen wir dia
Anwendung kennen, mehr Vokabeln, mehr Spaß?
Wir werden sehen.
In einem typischen Data Science Projekt haben wir folgende Schritte:
Zuerst müssen wir die Daten importieren. Aus einer Datei oder dem Netz oder einer Datenbank für gewöhnlich, so dass wir eine schöne, übersichtliche Tabelle haben. Im besten Fall versteht sich.
Danach muss aufgeräumt werden. Spalten mit Variablen und Zeilen mit Beobachtungen liegen uns vor, so dass wir uns im weiteren nicht mehr um nervige Aufräumarbeit kümmern müssen.
Haben wir aufgeräumt, so werden die Daten transformiert. Relevante Variablen werden zusammengefasst, neue Variablen entstehen aus alten durch Funktionen, summary statistics werden berechnet. Aufräumen und Transformation nennen wir zusammen: Wrangling.
Danach wird es spannend: Visualisation und Modelle erstellen steht im Vordergrund. Eine Grafik sagt mehr als 1000 Worte. Richtig romantisch, oder?
Kommunikation folgt dann. Kommuniziere deine Ergebnisse zu Anderen. Dies wird oft vernachlässigt, ist aber nicht immer so leicht wie man denkt.
Umfasst werden all diese Werkzeuge von der Programmierung. Um ein erfolgreicher Data Scientist zu sein, musst du nicht auch ein Experte in Sachen Programmierung sein. Aber ein besserer Programmierer zu sein hilft, da es dir erlaubt viele Aufgaben zu automatisieren und erheblich zu beschleunigen.
Es hilft natürlich sich mit den wichtigsten Konzepten von Teil 1 auseinandergesetzt zu haben. Neben R und RStudio brauchen wir noch das tidyverse Paket und viele weitere.
Ein Paket ist eine Kollektion von Funktionen, Daten und
Dokumentationen von R. Funktionen in R zu nutzen ist das Erfolgsgeheimis
von R.
Die meisten Pakete, die wir hier kennenlernen sind Teil vom
tidyverse Paket. Mit einer Zeile Code kannst du
tidyverse installieren.
install.packages("tidyverse")
Anschließend musst du natürlich noch das Paket laden:
library(tidyverse)
## Warning: Paket 'tidyverse' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'ggplot2' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'tibble' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'purrr' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'dplyr' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'stringr' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'lubridate' wurde unter R Version 4.2.3 erstellt
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.3 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.0
## ✔ ggplot2 3.4.4 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.0
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
So gehen wir immer vor, wenn Pakete benötigt werden. Sie erleichtern uns die Arbeit. Acht Pakete werden hier auf einmal geladen: ggplot2, tibble, tidyr, readr, purrr, dplyr, stringr und forcats. Bei fast jeder Analyse brauchst du sie.
In diesem Teil brauchen wir wahrscheinlich noch drei weitere Pakete:
install.packages(c("nycflights13", "gapminder", "Lahman"))
library(nycflights13)
## Warning: Paket 'nycflights13' wurde unter R Version 4.2.3 erstellt
library(gapminder)
## Warning: Paket 'gapminder' wurde unter R Version 4.2.3 erstellt
library(Lahman)
## Warning: Paket 'Lahman' wurde unter R Version 4.2.3 erstellt
Sie liefern uns Daten.
In diesem Kapitel fokusieren wir uns auf ggplot2. Das
Paket haben wir schon über library(tidyverse) geladen.
Falls nicht, holen wir dies schnell nach.
library(tidyverse)
Ein Paket musst du nur einmal installieren, es aber bei jeder neuen Session wieder laden. Hinzo kommen jetzt Daten von Pinguinen.
install.packages("palmerpenguins")
library(palmerpenguins)
## Warning: Paket 'palmerpenguins' wurde unter R Version 4.2.3 erstellt
Gibt es einen Zusammenhang zwischen der Flossenlänge eines Pinguins und seinem Gewicht? Wie sieht dieser Zusammenhang aus? Hängt er von der Spezie der Pinguine ab? Und vielleicht sogar von der Herkunft der Pinguine?
penguins data frameDieser Datensatz enthält 344 Zeilen und 7 Spalten.
Einen alternativen Blick kannst du mit glimpse() auf die
Daten werfen. Oder mit View(penguins).
glimpse(penguins)
## Rows: 344
## Columns: 8
## $ species <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adel…
## $ island <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgerse…
## $ bill_length_mm <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, …
## $ bill_depth_mm <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, …
## $ flipper_length_mm <int> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186…
## $ body_mass_g <int> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, …
## $ sex <fct> male, female, female, NA, female, male, female, male…
## $ year <int> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007…
species, flipper_length_mm und
body_mass_g gehören zu den Variablen. Mehr Infos unter
?penguins.
Wir wollen jetzt das Gewicht in Abhängigkeit von der Flossenlänge darstellen. Für alle drei Spezien getrennt.
ggplot erstellenDas erste Argument von ggplot() ist der Datensatz.
ggplot(data = penguins)
Es entsteht ein leerer Graph. Wir brauchen natürlich noch die
Variablen.
Das mapping Argument der ggplot() Funktion
definiert wie Variablen abgebildet werden, um sie zu visualisieren. Es
kommt immer mit der aes() Funktion und den x
und y Argumenten von aes() daher.
Bei uns soll die Flossenlänge auf der x-Achse sein und der Body Maß auf
der y-Achse.
ggplot(data = penguins,
mapping = aes(x = flipper_length_mm))
ggplot(data = penguins,
mapping = aes(x = flipper_length_mm, y = body_mass_g))
Unsere leere Leinwand wurde jetzt ein wenig gefüllt. Aber wo sind die
Pinguine? Noch nicht da, weil wir noch nicht gesagt haben wie die
Beobachtungen auf den Plot angewendet werden sollen. Um das zu machen
müssen wir ein geom definieren. Dieses Objekt benutzt
einen Plot um Daten zu repräsentieren. Oftmals wird der Typ des Plots an
geom_ angehängt wie z.B. geom_bar,
geom_line, geom_boxplot oder
geom_point.
Mithilfe von geom_point werden Punkte zum Plot hinzugefügt,
so dass ein Scatterplot entsteht. Insgesamt gibt es sehr viele
geom Funktionen.
ggplot(data = penguins,
mapping = aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point()
## Warning: Removed 2 rows containing missing values (`geom_point()`).
Wir haben einen einigermaßen linearen, positiven Zusammenhang.
Genauso wie erwartet zwischen Flossenlänge und Gewicht. Nur die
verschiedenen Spezien fehlen noch.
Eine Fehlermeldung wurde aber auch noch ausgegeben. Hier fehlen 2 Werte,
siehe Tabelle oder hier (der Code ist neu für uns. Keine Sorge,
Erklärung kommt noch).
penguins |>
select(species, flipper_length_mm, body_mass_g) |>
filter(is.na(body_mass_g) | is.na(flipper_length_mm))
## # A tibble: 2 × 3
## species flipper_length_mm body_mass_g
## <fct> <int> <int>
## 1 Adelie NA NA
## 2 Gentoo NA NA
Missing Values haben ihre Daseinsberechtigung. Kein Witz.
Den Zusammenhang zwischen zwei Variablen darzustellen ist schön und
gut. Doch oftmals fragt man sich, ob es nicht noch weitere Variablen
gibt, die den Zusammenhang erklären oder ändern.
In unserem Beispiel nehmen wir noch die Spezie hinzu. Aber wo genau? In
das aesthetic mapping, in die aes() Funktion.
ggplot(data = penguins,
mapping = aes(x = flipper_length_mm, y = body_mass_g,
color = species)) +
geom_point()
## Warning: Removed 2 rows containing missing values (`geom_point()`).
Da eine weitere Variable in das aes() aufgenommen wurde,
hat ggplot2 dieser automatisch einen einzigartigen Wert
zugewiesen, hier eine eine Farbe jedem Level der Variable. Das nennt
sich scaling. Auch eine Legende wurde automatisch
hinzugefügt. Weitere Schichten (layers) sind möglich.
Zum Beispiel eine glatte Kurve, die den Zusammenhang anschaulich
wiedergibt. Ein neues geom wird hinzugefügt:
geom_smooth().
ggplot(data = penguins,
mapping = aes(x = flipper_length_mm, y = body_mass_g,
color = species)) +
geom_point() +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Removed 2 rows containing non-finite values (`stat_smooth()`).
## Warning: Removed 2 rows containing missing values (`geom_point()`).
Wir wollen aber nur eine Kurve für alle Spezien zusammen. Wie geht das denn?
Wir haben aes mappings in ggplot() definiert, im
globalen Level. Jetzt werden sie weiter vererbt von jeder folgenden
geom Schicht des Plots. Wir können aber jeder geom
Funktion in ggplot2 ein lokales mapping
verpassen. Wollen wir farbige Punkte für die verschiedenen Spezien, aber
eine Kurve für alle, so können wir nur für die Punkte
geom_point() eine Unterscheidung vornehmen, durch
color = species.
ggplot(data = penguins,
mapping = aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point(mapping = aes(color = species)) +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Removed 2 rows containing non-finite values (`stat_smooth()`).
## Warning: Removed 2 rows containing missing values (`geom_point()`).
So gefällt uns das doch schon viel besser. Uns reichen verschiedene Farben aber nicht aus. Wir wollen auch verschiedene Symbole. Es gibt auch Farbenblinde, auch an die denken wir jetzt einmal.
Zusätzlich können wir auch species zum
shape aes hinzufügen.
ggplot(data = penguins,
mapping = aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point(mapping = aes(color = species, shape = species)) +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Removed 2 rows containing non-finite values (`stat_smooth()`).
## Warning: Removed 2 rows containing missing values (`geom_point()`).
Als weitere Schicht können wir noch die labs() Funktion
hinzufügen. Alles (Labels) sieht dann noch schöner aus.
ggplot(penguins,
aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point(aes(color = species, shape = species)) +
geom_smooth() +
labs(
title = "Body mass and flipper length",
subtitle = "Dimensions for Adelie, Chinstrap, and Gentoo Penguins",
x = "Flipper length (mm)",
y = "Body mass (g)",
color = "Species",
shape = "Species"
)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Removed 2 rows containing non-finite values (`stat_smooth()`).
## Warning: Removed 2 rows containing missing values (`geom_point()`).
Die Ausdrücke data = penguins und
mapping = aes() können wir auch verkürzen zu
penguins und aes().
ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point()
Später lernen wir die Pipe kennen, dann können wir weiter vereinfachen.
penguins |>
ggplot(aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point()
Die Visualisierung hängt natürlich vom Datentyp bzw. dem Skalenniveau ab: kategorial oder numerisch.
Für kategoriale Variablen bietet sich ein bar chart an, ein
Säulendiagramm. Es zählt die Häufigkeit der Variable x.
ggplot(penguins, aes(x = species)) +
geom_bar()
Die Reihenfolge wird oft durch das Alphabet vorgenommen, kann aber nach Häufigkeit vorgenommen werden. Dafür müssen wir aber die Variable zu einem Faktor transformieren. Danach können wir neu sortieren.
ggplot(penguins, aes(x = fct_infreq(species))) +
geom_bar()
Mehr dazu später.
Um die Verteilung einer stetigen Variable zu visualisieren, benutze ein Histogramm oder einen density (Dichte) plot.
ggplot(penguins, aes(x = body_mass_g)) +
geom_histogram(binwidth = 200)
## Warning: Removed 2 rows containing non-finite values (`stat_bin()`).
ggplot(penguins, aes(x = body_mass_g)) +
geom_density()
## Warning: Removed 2 rows containing non-finite values (`stat_density()`).
Wir sehen, dass 39 Pinguine ein Gewicht zwischen 3500 g und 3700 g haben (Klassengrenzen, Klassenbreite = 200).
penguins |>
count(cut_width(body_mass_g, 200))
## # A tibble: 19 × 2
## `cut_width(body_mass_g, 200)` n
## <fct> <int>
## 1 [2.7e+03,2.9e+03] 7
## 2 (2.9e+03,3.1e+03] 10
## 3 (3.1e+03,3.3e+03] 23
## 4 (3.3e+03,3.5e+03] 38
## 5 (3.5e+03,3.7e+03] 39
## 6 (3.7e+03,3.9e+03] 37
## 7 (3.9e+03,4.1e+03] 28
## 8 (4.1e+03,4.3e+03] 25
## 9 (4.3e+03,4.5e+03] 20
## 10 (4.5e+03,4.7e+03] 22
## 11 (4.7e+03,4.9e+03] 21
## 12 (4.9e+03,5.1e+03] 17
## 13 (5.1e+03,5.3e+03] 13
## 14 (5.3e+03,5.5e+03] 14
## 15 (5.5e+03,5.7e+03] 16
## 16 (5.7e+03,5.9e+03] 6
## 17 (5.9e+03,6.1e+03] 5
## 18 (6.1e+03,6.3e+03] 1
## 19 <NA> 2
Probiere verschiedene Bandbreiten aus, um das anschaulichste Histogramm zu erhalten.
ggplot(penguins, aes(x = body_mass_g)) +
geom_histogram(binwidth = 20)
## Warning: Removed 2 rows containing non-finite values (`stat_bin()`).
ggplot(penguins, aes(x = body_mass_g)) +
geom_histogram(binwidth = 200)
## Warning: Removed 2 rows containing non-finite values (`stat_bin()`).
ggplot(penguins, aes(x = body_mass_g)) +
geom_histogram(binwidth = 2000)
## Warning: Removed 2 rows containing non-finite values (`stat_bin()`).
Dafür brauchen wir natürlich mindestens zwei Variablen.
Boxplots natürlich.
ggplot(penguins, aes(x = species, y = body_mass_g)) +
geom_boxplot()
## Warning: Removed 2 rows containing non-finite values (`stat_boxplot()`).
Alternativ bieten sich Häufigkeits-Polygonzüge an, mit
geom_freqpoly(). Statt konstante Höhen werden hier Linien
benutzt.
ggplot(penguins, aes(x = body_mass_g, color = species)) +
geom_freqpoly(binwidth = 200, linewidth = 0.75)
## Warning: Removed 2 rows containing non-finite values (`stat_bin()`).
Wir können die Dicke der Linien mit linewidth anpassen.
Wir können density plots anschaulich überlappen, sie
transparent machen, Farben benutzen und sie befüllen. Rund werden sie
auch noch. Der “Transparenzwert” alpha liegt zwischen 0
(komplett transparent) und 1.
ggplot(penguins, aes(x = body_mass_g, color = species, fill = species)) +
geom_density(alpha = 0.5)
## Warning: Removed 2 rows containing non-finite values (`stat_density()`).
Die erste Variable wird unter x aes auf der
x-Achse abgetragen und die zweite Variable wird dem fill
aes zugeordnet. Farblich wird dann jeder Balken noch einmal
unterteilt (bzgl. der Variable).
Die Plots sind dann selbsterklärend.
ggplot(penguins, aes(x = island, fill = species)) +
geom_bar()
ggplot(penguins, aes(x = island, fill = species)) +
geom_bar(position = "fill")
Ein Scatterplot ist die weitverbreiteste Darstellungsart von zwei numerischen Variablen.
ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point()
## Warning: Removed 2 rows containing missing values (`geom_point()`).
Eine Möglichkeit ist es, die Variablen einem aes
zuzuordnen. Neben x-Achse und y-Achse haben wir so insgesamt 4
Variablen: species und island noch dazu.
ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point(aes(color = species, shape = island))
## Warning: Removed 2 rows containing missing values (`geom_point()`).
Zu viele aes machen einen Plot aber unübersichtlich.
Eine andere, nützliche Möglichkeit bei kategorialen Variablen sind
Subplots, facets genannt.
Benutze dafür facet_wrap() Das erste Argument ist eine
Tilde, das zweite der Variablenname. Diese Variable sollte natürlich
kategorial sein.
ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point(aes(color = species, shape = species)) +
facet_wrap(~island)
## Warning: Removed 2 rows containing missing values (`geom_point()`).
ggsave() speichert.
ggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g)) +
geom_point()
## Warning: Removed 2 rows containing missing values (`geom_point()`).
#> Warning: Removed 2 rows containing missing values (`geom_point()`).
ggsave(filename = "my-plot.png")
## Saving 7 x 5 in image
## Warning: Removed 2 rows containing missing values (`geom_point()`).
#> Saving 6 x 4 in image
#> Warning: Removed 2 rows containing missing values (`geom_point()`).
Dieser Befehl speichert den Plot in der working directory.
Notfalls sucht die Datei auf eurem Computer. Mehr dazu unter
?sec-workflow-scripts.
Denke daran deine Klammern wieder zu schließen. Wenn du ein
+ siehst. R wartet darauf, dass du deinen Befehl zu Ende
schreibst. Das + steht am Ende der Zeile, nicht am
Anfang.
Scheue dich nicht google.com zu benutzen, hier findest du wirklich
alles. Oder Chat GPT.
Visualisierung ist schön und gut. Aber es ist doch sehr selten, dass du deine Daten genau in der gewünschten Form bekommst. Oft musst du neue Variablen erstellen, andere zusammenfassen, oder umbenennen, Werte in ihnen neu sortieren. All das lernen wir in diesem Kapitel. Dafür brauchen wir das dplyr Paket und einen neuen Datensatz.
library(nycflights13)
library(tidyverse)
Der Datensatz enthält 336 776 Flüge, die 2013 von NYC aus geflogen wurden.
flights
## # A tibble: 336,776 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Dieser Datensatz sieht jetzt optisch ein wenig anders aus, als wir es
(von früher) gewohnt sind. Weil er ein tibble ist, ein
spezieller Typ von Data Frame. Es werden hier nur die ersten
Zeilen angezeigt. Auch nicht alle Spalten, gersde so viele wie auf den
Bildschirm passen oder ins Fenster. Der Datentyp ist für große
Datensätze gemacht. View(flights) bietet eine interaktive
Ansicht, ähnlich wie in Excel. Mit
print(flights, width = Inf) kannst du alle Spalten anzeigen
lassen. Oder mit glimpse().
glimpse(flights)
## Rows: 336,776
## Columns: 19
## $ year <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2…
## $ month <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ day <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ dep_time <int> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, …
## $ sched_dep_time <int> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, …
## $ dep_delay <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1…
## $ arr_time <int> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849,…
## $ sched_arr_time <int> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851,…
## $ arr_delay <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -1…
## $ carrier <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "…
## $ flight <int> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 4…
## $ tailnum <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N394…
## $ origin <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA",…
## $ dest <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD",…
## $ air_time <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 1…
## $ distance <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, …
## $ hour <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6…
## $ minute <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0…
## $ time_hour <dttm> 2013-01-01 05:00:00, 2013-01-01 05:00:00, 2013-01-01 0…
Hier ist auch der Typ jeder Variable angegeben:
<int> für integer, <dbl>
für double, <chr> für character und
<dttm> für date-time. Wichtig sind sie, da
die Operationen, die du auf sie ausführst, vom Datentyp abhängen.
Die wichtigsten dplyr verbs lernen wir hier kennen. Sie erlauben uns die meiste Arbeit der Datenmanipulation zu verrichten. Was haben sie alle gemeinsam?
Das erste Argument ist ein Data Frame.
Die nachfolgenden Argumente beschreiben was mit dem Data Frame zu machen ist. Sie benutzen die Variablennamen hierbei (ohne Anführungszeichen).
Das Ergebnis ist ein neuer Data Frame.
Da das erste Argument immer ein Data Frame ist, und auch der
Output, arbeiten dplyr verbs gut mit der pipe,
|>:
x |> f(y) ist äquivalent zu f(x, y)
und
x |> f(y) |> g(z) ist äquivalent zu
g(f(x, y), z).
Ausgesprochen wir die pipe als “dann” oder “then”.
flights |>
filter(dest == "IAH") |>
group_by(year, month, day) |>
summarize(
arr_delay = mean(arr_delay, na.rm = TRUE)
)
## `summarise()` has grouped output by 'year', 'month'. You can override using the
## `.groups` argument.
## # A tibble: 365 × 4
## # Groups: year, month [12]
## year month day arr_delay
## <int> <int> <int> <dbl>
## 1 2013 1 1 17.8
## 2 2013 1 2 7
## 3 2013 1 3 18.3
## 4 2013 1 4 -3.2
## 5 2013 1 5 20.2
## 6 2013 1 6 9.28
## 7 2013 1 7 -7.74
## 8 2013 1 8 7.79
## 9 2013 1 9 18.1
## 10 2013 1 10 6.68
## # ℹ 355 more rows
fd <- data.frame(A = c(2009, 2009, 2009, 2010, 2010), B = c(3,4,5,6,8))
fd|>
group_by(A)|>
summarise(B = mean(B))
## # A tibble: 2 × 2
## A B
## <dbl> <dbl>
## 1 2009 4
## 2 2010 7
Der Code startet mit dem flights Datensatz, dann wird
gefiltert, dann gruppiert, dann zusammengefasst.
dplyr’s verbs sind in 4 Gruppen organisiert: rows,
columns, groups, tables.
Die wichtigsten Verben, die auf Reihen angewendet werden, sind
filter() und arrange(). Beide Funktionen
affektieren nur die Zeilen. Die Spalten bleiben unberührt.
distinct findet Zeilen mit einzigartigen Values.
Es kann auch die Spalten verändern.
filter()Wir behalten die Reihen bei, die bestimmte Werte der Spalten haben. Das erste Argument ist ein Data Frame, danach folgen die Bedingugnen, die erfüllt sein müssen. Alle Flüge mit mehr als 120 Minuten Verspätung:
flights |>
filter(arr_delay > 120)
## # A tibble: 10,034 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 811 630 101 1047 830
## 2 2013 1 1 848 1835 853 1001 1950
## 3 2013 1 1 957 733 144 1056 853
## 4 2013 1 1 1114 900 134 1447 1222
## 5 2013 1 1 1505 1310 115 1638 1431
## 6 2013 1 1 1525 1340 105 1831 1626
## 7 2013 1 1 1549 1445 64 1912 1656
## 8 2013 1 1 1558 1359 119 1718 1515
## 9 2013 1 1 1732 1630 62 2028 1825
## 10 2013 1 1 1803 1620 103 2008 1750
## # ℹ 10,024 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Natürlich kannst du auch die bekannten Operatoren:
>>=<<===!=benutzen, und auch & (und) oder |
(oder).
# Flights that departed on January 1
flights |>
filter(month == 1 & day == 1)
## # A tibble: 842 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 832 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Kombinationen sind also natürlich möglich.
# Flights that departed in January or February
flights |>
filter(month == 1 | month == 2)
## # A tibble: 51,955 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 51,945 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Bekannt ist auch der %in% Operator, der die Kombination
| und == ersetzt. Zeilen werden behalten, bei
denen die Variable einem der Werte auf der rechten Seite entspricht.
# A shorter way to select flights that departed in January or February
flights |>
filter(month %in% c(1, 2))
## # A tibble: 51,955 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 51,945 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
dplyr Funktionen modifizieren niemals ihren input
data frame, flights bleibt also erhalten. Um das
Ergebnis zu speichern, musst du also den assign Operator
<- bemühen.
jan1 <- flights |>
filter(month == 1 & day == 1)
jan1
## # A tibble: 842 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 832 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
arrange()arrange() ändert die Reihenfolge der Reihen, basierend
auf den Werten der Spalten. Als Argumente kommen nach dem Data
Frame die Spalten, die sortiert werden sollen. Von klein nach groß.
Erst Jahr, dann Monat, usw.
flights |>
arrange(year, month, day, dep_time)
## # A tibble: 336,776 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Du kannst natürlich auch andersrum sortieren mit
desc().
flights |>
arrange(desc(dep_delay))
## # A tibble: 336,776 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 9 641 900 1301 1242 1530
## 2 2013 6 15 1432 1935 1137 1607 2120
## 3 2013 1 10 1121 1635 1126 1239 1810
## 4 2013 9 20 1139 1845 1014 1457 2210
## 5 2013 7 22 845 1600 1005 1044 1815
## 6 2013 4 10 1100 1900 960 1342 2211
## 7 2013 3 17 2321 810 911 135 1020
## 8 2013 6 27 959 1900 899 1236 2226
## 9 2013 7 22 2257 759 898 121 1026
## 10 2013 12 5 756 1700 896 1058 2020
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
arrange() und filter() können natürlich
kombiniert werden.
flights |>
filter(dep_delay <= 10 & dep_delay >= -10) |>
arrange(desc(arr_delay))
## # A tibble: 239,109 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 11 1 658 700 -2 1329 1015
## 2 2013 4 18 558 600 -2 1149 850
## 3 2013 7 7 1659 1700 -1 2050 1823
## 4 2013 7 22 1606 1615 -9 2056 1831
## 5 2013 9 19 648 641 7 1035 810
## 6 2013 4 18 655 700 -5 1213 950
## 7 2013 6 30 1423 1425 -2 1816 1554
## 8 2013 6 24 1523 1520 3 1931 1710
## 9 2013 3 18 1844 1847 -3 39 2219
## 10 2013 7 1 905 905 0 1443 1223
## # ℹ 239,099 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
distinct()Einzigartige Zeilen werden mit distinct() gefunden.
Meistens wollen wir jedoch nur eine einzigartige Kombination von ein
paar Variablen. Dann brauchen wir diese natürlich als Argument für
distinct.
# This would remove any duplicate rows if there were any
flights |>
distinct()
## # A tibble: 336,776 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Oder natürlich.
# This finds all unique origin and destination pairs.
flights |>
distinct(origin, dest)
## # A tibble: 224 × 2
## origin dest
## <chr> <chr>
## 1 EWR IAH
## 2 LGA IAH
## 3 JFK MIA
## 4 JFK BQN
## 5 LGA ATL
## 6 EWR ORD
## 7 EWR FLL
## 8 LGA IAD
## 9 JFK MCO
## 10 LGA ORD
## # ℹ 214 more rows
Für die Anzahl an Duplikaten benutze aber besser
count().
Es gibt vier verschiedene Verben, die Spalten beeinträchtigen, ohne
die Zeilen zu vertauschen:
- mutate() - select() -
rename()
- relocate()
mutate()mutate() fügt eine neue Spalte hinzu, die aus den
bereits existierenden berechnet wird. Wir berechnen einfache Dinge, wie
Differenzen und Quotienten, z.B. wie lange ein Flug in der Luft war oder
die Geschwindigkeit in Meilen pro Stunde.
flights |>
mutate(
gain = dep_delay - arr_delay,
speed = distance / air_time * 60
)
## # A tibble: 336,776 × 21
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 13 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>, gain <dbl>, speed <dbl>
mutate() hängt diese neuen Variablen rechts an die
Tabellen dran. Mit .before können wir die Variablen auch
links dran setzen.
flights |>
mutate(
gain = dep_delay - arr_delay,
speed = distance / air_time * 60,
.before = 1
)
## # A tibble: 336,776 × 21
## gain speed year month day dep_time sched_dep_time dep_delay arr_time
## <dbl> <dbl> <int> <int> <int> <int> <int> <dbl> <int>
## 1 -9 370. 2013 1 1 517 515 2 830
## 2 -16 374. 2013 1 1 533 529 4 850
## 3 -31 408. 2013 1 1 542 540 2 923
## 4 17 517. 2013 1 1 544 545 -1 1004
## 5 19 394. 2013 1 1 554 600 -6 812
## 6 -16 288. 2013 1 1 554 558 -4 740
## 7 -24 404. 2013 1 1 555 600 -5 913
## 8 11 259. 2013 1 1 557 600 -3 709
## 9 5 405. 2013 1 1 557 600 -3 838
## 10 -10 319. 2013 1 1 558 600 -2 753
## # ℹ 336,766 more rows
## # ℹ 12 more variables: sched_arr_time <int>, arr_delay <dbl>, carrier <chr>,
## # flight <int>, tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>,
## # distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
Der . ist ein Zeichen, so dass .before ein
Argument der Funktion ist, nicht der Name einer neuen Variable.
.after kannst du auch benutzen, um nach einer Variable
deine neue Spalte zu setzen. Statt der Position, kannst du hier auch den
Namen der Variable setzen (z.B. day).
flights |>
mutate(
gain = dep_delay - arr_delay,
speed = distance / air_time * 60,
.after = day
)
## # A tibble: 336,776 × 21
## year month day gain speed dep_time sched_dep_time dep_delay arr_time
## <int> <int> <int> <dbl> <dbl> <int> <int> <dbl> <int>
## 1 2013 1 1 -9 370. 517 515 2 830
## 2 2013 1 1 -16 374. 533 529 4 850
## 3 2013 1 1 -31 408. 542 540 2 923
## 4 2013 1 1 17 517. 544 545 -1 1004
## 5 2013 1 1 19 394. 554 600 -6 812
## 6 2013 1 1 -16 288. 554 558 -4 740
## 7 2013 1 1 -24 404. 555 600 -5 913
## 8 2013 1 1 11 259. 557 600 -3 709
## 9 2013 1 1 5 405. 557 600 -3 838
## 10 2013 1 1 -10 319. 558 600 -2 753
## # ℹ 336,766 more rows
## # ℹ 12 more variables: sched_arr_time <int>, arr_delay <dbl>, carrier <chr>,
## # flight <int>, tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>,
## # distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
Alternativ kannst du kontrollieren, welche Variablen behalten werden
sollen, mit dem .keep Argument. Ein nützliches Argument ist
used, welches dir die Inputs und Outputs deiner
Berechnungen anzeigt.
flights |>
mutate(
gain = dep_delay - arr_delay,
hours = air_time / 60,
gain_per_hour = gain / hours,
.keep = "used"
)
## # A tibble: 336,776 × 6
## dep_delay arr_delay air_time gain hours gain_per_hour
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2 11 227 -9 3.78 -2.38
## 2 4 20 227 -16 3.78 -4.23
## 3 2 33 160 -31 2.67 -11.6
## 4 -1 -18 183 17 3.05 5.57
## 5 -6 -25 116 19 1.93 9.83
## 6 -4 12 150 -16 2.5 -6.4
## 7 -5 19 158 -24 2.63 -9.11
## 8 -3 -14 53 11 0.883 12.5
## 9 -3 -8 140 5 2.33 2.14
## 10 -2 8 138 -10 2.3 -4.35
## # ℹ 336,766 more rows
select()Oftmals kriegt man unendlich viele Variablen an die Hand, von denen
uns aber nur ein paar interessieren. select() gibt uns nur
einige von ihnen wieder. In unserem Beispiel haben wir nur 19 Variablen,
aber die Idee zählt.
# Select columns by name
flights |>
select(year, month, day)
## # A tibble: 336,776 × 3
## year month day
## <int> <int> <int>
## 1 2013 1 1
## 2 2013 1 1
## 3 2013 1 1
## 4 2013 1 1
## 5 2013 1 1
## 6 2013 1 1
## 7 2013 1 1
## 8 2013 1 1
## 9 2013 1 1
## 10 2013 1 1
## # ℹ 336,766 more rows
# Select all columns between year and day (inclusive)
flights |>
select(year:day)
## # A tibble: 336,776 × 3
## year month day
## <int> <int> <int>
## 1 2013 1 1
## 2 2013 1 1
## 3 2013 1 1
## 4 2013 1 1
## 5 2013 1 1
## 6 2013 1 1
## 7 2013 1 1
## 8 2013 1 1
## 9 2013 1 1
## 10 2013 1 1
## # ℹ 336,766 more rows
# Select all columns except those from year to day (inclusive)
flights |>
select(!year:day)
## # A tibble: 336,776 × 16
## dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier
## <int> <int> <dbl> <int> <int> <dbl> <chr>
## 1 517 515 2 830 819 11 UA
## 2 533 529 4 850 830 20 UA
## 3 542 540 2 923 850 33 AA
## 4 544 545 -1 1004 1022 -18 B6
## 5 554 600 -6 812 837 -25 DL
## 6 554 558 -4 740 728 12 UA
## 7 555 600 -5 913 854 19 B6
## 8 557 600 -3 709 723 -14 EV
## 9 557 600 -3 838 846 -8 B6
## 10 558 600 -2 753 745 8 AA
## # ℹ 336,766 more rows
## # ℹ 9 more variables: flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## # air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
# Select all columns that are characters
flights |>
select(where(is.character))
## # A tibble: 336,776 × 4
## carrier tailnum origin dest
## <chr> <chr> <chr> <chr>
## 1 UA N14228 EWR IAH
## 2 UA N24211 LGA IAH
## 3 AA N619AA JFK MIA
## 4 B6 N804JB JFK BQN
## 5 DL N668DN LGA ATL
## 6 UA N39463 EWR ORD
## 7 B6 N516JB EWR FLL
## 8 EV N829AS LGA IAD
## 9 B6 N593JB JFK MCO
## 10 AA N3ALAA LGA ORD
## # ℹ 336,766 more rows
Viele weitere Hilfsfunktionen arbeiten mit select():
# starts_with("tai"): matches names that begin with “tai”
flights |>
select(starts_with("tai"))
## # A tibble: 336,776 × 1
## tailnum
## <chr>
## 1 N14228
## 2 N24211
## 3 N619AA
## 4 N804JB
## 5 N668DN
## 6 N39463
## 7 N516JB
## 8 N829AS
## 9 N593JB
## 10 N3ALAA
## # ℹ 336,766 more rows
starts_with("abc")ends_with("xyz")contains("ijk")num_range("x", 1:3): matcht x1,
x2, x3Variablen neu benennen über select() durch
=.
flights |>
select(tail_num = tailnum)
## # A tibble: 336,776 × 1
## tail_num
## <chr>
## 1 N14228
## 2 N24211
## 3 N619AA
## 4 N804JB
## 5 N668DN
## 6 N39463
## 7 N516JB
## 8 N829AS
## 9 N593JB
## 10 N3ALAA
## # ℹ 336,766 more rows
rename()flights |>
rename(tail_num = tailnum)
## # A tibble: 336,776 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tail_num <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Funktioniert wie select(), aber behält alle nicht
ausgewähle Variablen bei.
relocate()Bewege Variablen nach vorne.
flights |>
relocate(time_hour, air_time)
## # A tibble: 336,776 × 19
## time_hour air_time year month day dep_time sched_dep_time
## <dttm> <dbl> <int> <int> <int> <int> <int>
## 1 2013-01-01 05:00:00 227 2013 1 1 517 515
## 2 2013-01-01 05:00:00 227 2013 1 1 533 529
## 3 2013-01-01 05:00:00 160 2013 1 1 542 540
## 4 2013-01-01 05:00:00 183 2013 1 1 544 545
## 5 2013-01-01 06:00:00 116 2013 1 1 554 600
## 6 2013-01-01 05:00:00 150 2013 1 1 554 558
## 7 2013-01-01 06:00:00 158 2013 1 1 555 600
## 8 2013-01-01 06:00:00 53 2013 1 1 557 600
## 9 2013-01-01 06:00:00 140 2013 1 1 557 600
## 10 2013-01-01 06:00:00 138 2013 1 1 558 600
## # ℹ 336,766 more rows
## # ℹ 12 more variables: dep_delay <dbl>, arr_time <int>, sched_arr_time <int>,
## # arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>, origin <chr>,
## # dest <chr>, distance <dbl>, hour <dbl>, minute <dbl>
Oder an einen bestimmten Ort mit .before und
.after.
flights |>
relocate(year:dep_time, .after = time_hour)
## # A tibble: 336,776 × 19
## sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight
## <int> <dbl> <int> <int> <dbl> <chr> <int>
## 1 515 2 830 819 11 UA 1545
## 2 529 4 850 830 20 UA 1714
## 3 540 2 923 850 33 AA 1141
## 4 545 -1 1004 1022 -18 B6 725
## 5 600 -6 812 837 -25 DL 461
## 6 558 -4 740 728 12 UA 1696
## 7 600 -5 913 854 19 B6 507
## 8 600 -3 709 723 -14 EV 5708
## 9 600 -3 838 846 -8 B6 79
## 10 600 -2 753 745 8 AA 301
## # ℹ 336,766 more rows
## # ℹ 12 more variables: tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>,
## # distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>, year <int>,
## # month <int>, day <int>, dep_time <int>
flights |>
relocate(starts_with("arr"), .before = dep_time)
## # A tibble: 336,776 × 19
## year month day arr_time arr_delay dep_time sched_dep_time dep_delay
## <int> <int> <int> <int> <dbl> <int> <int> <dbl>
## 1 2013 1 1 830 11 517 515 2
## 2 2013 1 1 850 20 533 529 4
## 3 2013 1 1 923 33 542 540 2
## 4 2013 1 1 1004 -18 544 545 -1
## 5 2013 1 1 812 -25 554 600 -6
## 6 2013 1 1 740 12 554 558 -4
## 7 2013 1 1 913 19 555 600 -5
## 8 2013 1 1 709 -14 557 600 -3
## 9 2013 1 1 838 -8 557 600 -3
## 10 2013 1 1 753 8 558 600 -2
## # ℹ 336,766 more rows
## # ℹ 11 more variables: sched_arr_time <int>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
dplyr wird noch mächtiger durch die Arbeit mit
Gruppen.
group_byBenutze group_by() um deinen Datensatz in Gruppen zu
unterteilen, die dir bei deiner Analyse helfen.
flights |>
group_by(month)
## # A tibble: 336,776 × 19
## # Groups: month [12]
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Die Daten werden nicht verändert, aber der Output ist jetzt
grouped by month. grouped_by() verändert das
Verhalten nachkommender Verben.
summarize()Die wichtigste gruppierte Operation ist eine Zusammenfassung (summary), welche jede Gruppe zu einer einzelnen Zeile zusammenfasst. Die durchschnittliche departure delay (Abflugverzögerung) nach Monaten kann so berechnet werden.
flights |>
group_by(month) |>
summarize(
delay = mean(dep_delay, na.rm = TRUE)
)
## # A tibble: 12 × 2
## month delay
## <int> <dbl>
## 1 1 10.0
## 2 2 10.8
## 3 3 13.2
## 4 4 13.9
## 5 5 13.0
## 6 6 20.8
## 7 7 21.7
## 8 8 12.6
## 9 9 6.72
## 10 10 6.24
## 11 11 5.44
## 12 12 16.6
In einem summarize() Aufruf kannst du jede beliebige
Anzahl von Summaries ausgeben lassen. n() gibt die
Anzahl von Zeilen in jeder Gruppe wieder.
flights |>
group_by(month) |>
summarize(
delay = mean(dep_delay, na.rm = TRUE),
n = n()
)
## # A tibble: 12 × 3
## month delay n
## <int> <dbl> <int>
## 1 1 10.0 27004
## 2 2 10.8 24951
## 3 3 13.2 28834
## 4 4 13.9 28330
## 5 5 13.0 28796
## 6 6 20.8 28243
## 7 7 21.7 29425
## 8 8 12.6 29327
## 9 9 6.72 27574
## 10 10 6.24 28889
## 11 11 5.44 27268
## 12 12 16.6 28135
slice_ FunktionenFünf Funktionen erlauben es dir spezielle Zeilen innerhalb jeder Gruppe wiederzugeben.
df |> slice_head(n = 1) erste Zeile jeder
Gruppedf |> slice_tail(n = 1) letzte Zeile jeder
Gruppedf |> slice_min(x, n = 1) Zeile mit kleinstem Wert
von xdf |> slice_max(x, n = 1) Zeile mit größtem
Wertdf |> slice_sample(n = 1) zufällige ZeileÜber n kannst du auch mehr als eine Zeile dir ausgeben
lassen. Stattdessen kannst du dir auch einen Prozentsatz ausgeben
lassen: prop = 0.1 für 10%.
flights |>
group_by(dest) |>
slice_max(arr_delay, n = 1)
## # A tibble: 108 × 19
## # Groups: dest [105]
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 7 22 2145 2007 98 132 2259
## 2 2013 7 23 1139 800 219 1250 909
## 3 2013 1 25 123 2000 323 229 2101
## 4 2013 8 17 1740 1625 75 2042 2003
## 5 2013 7 22 2257 759 898 121 1026
## 6 2013 7 10 2056 1505 351 2347 1758
## 7 2013 8 13 1156 832 204 1417 1029
## 8 2013 2 21 1728 1316 252 1839 1413
## 9 2013 12 1 1504 1056 248 1628 1230
## 10 2013 4 10 25 1900 325 136 2045
## # ℹ 98 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Eine ähnliche Ausgabe kriegst du über summarize(). Nur
einmal kriegst du die ganze Zeile, einmal die single
summary.
flights |>
group_by(dest) |>
summarize(max_delay = max(arr_delay, na.rm = TRUE))
## Warning: There was 1 warning in `summarize()`.
## ℹ In argument: `max_delay = max(arr_delay, na.rm = TRUE)`.
## ℹ In group 52: `dest = "LGA"`.
## Caused by warning in `max()`:
## ! kein nicht-fehlendes Argument für max; gebe -Inf zurück
## # A tibble: 105 × 2
## dest max_delay
## <chr> <dbl>
## 1 ABQ 153
## 2 ACK 221
## 3 ALB 328
## 4 ANC 39
## 5 ATL 895
## 6 AUS 349
## 7 AVL 228
## 8 BDL 266
## 9 BGR 238
## 10 BHM 291
## # ℹ 95 more rows
Du kannst natürlich auch nach mehreren Variablen gruppieren. Eine Gruppe für jeden Tag.
daily <- flights |>
group_by(year, month, day)
daily
## # A tibble: 336,776 × 19
## # Groups: year, month, day [365]
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Wenn du ein tibble zusammenfasst, gruppiert mit mehr als einer Variable, kriegst du einen Hinweis nach welcher Variable vor der letzten zuvor gruppiert wurde.
daily_flights <- daily |>
summarize(
n = n()
)
## `summarise()` has grouped output by 'year', 'month'. You can override using the
## `.groups` argument.
daily_flights |>
print(n = 30)
## # A tibble: 365 × 4
## # Groups: year, month [12]
## year month day n
## <int> <int> <int> <int>
## 1 2013 1 1 842
## 2 2013 1 2 943
## 3 2013 1 3 914
## 4 2013 1 4 915
## 5 2013 1 5 720
## 6 2013 1 6 832
## 7 2013 1 7 933
## 8 2013 1 8 899
## 9 2013 1 9 902
## 10 2013 1 10 932
## 11 2013 1 11 930
## 12 2013 1 12 690
## 13 2013 1 13 828
## 14 2013 1 14 928
## 15 2013 1 15 894
## 16 2013 1 16 901
## 17 2013 1 17 927
## 18 2013 1 18 924
## 19 2013 1 19 674
## 20 2013 1 20 786
## 21 2013 1 21 912
## 22 2013 1 22 890
## 23 2013 1 23 897
## 24 2013 1 24 925
## 25 2013 1 25 922
## 26 2013 1 26 680
## 27 2013 1 27 823
## 28 2013 1 28 923
## 29 2013 1 29 890
## 30 2013 1 30 900
## # ℹ 335 more rows
Diese Meldung kannst du unterdrücken durch:
daily_flights <- daily |>
summarize(
n = n(),
.groups = "drop_last"
)
Und erhälst so dann:
daily_flights |>
print(n = 5)
## # A tibble: 365 × 4
## # Groups: year, month [12]
## year month day n
## <int> <int> <int> <int>
## 1 2013 1 1 842
## 2 2013 1 2 943
## 3 2013 1 3 914
## 4 2013 1 4 915
## 5 2013 1 5 720
## # ℹ 360 more rows
Durch ungroup() kannst du die Gruppierung außerhalb von
summarize() entfernen.
daily |>
ungroup() |>
summarize(
delay = mean(dep_delay, na.rm = TRUE),
flights = n()
)
## # A tibble: 1 × 2
## delay flights
## <dbl> <int>
## 1 12.6 336776
Wenn du einen ungruppierten Data Frame zusammenfasst, kriegst du logischerweise nur eine Zeile als Ausgabe. Als hättest du nur eine Gruppe.
Bei Aggregation ist es immer sinnvoll eine count Variable
mit einzubauen.
Schauen wir uns die Flugzeuge an, identifiziert durch ihre tail
number, die die höchsten durchschnittlichen Verspätungen haben.
delays <- flights |>
filter(!is.na(arr_delay), !is.na(tailnum)) |>
group_by(tailnum) |>
summarize(
delay = mean(arr_delay, na.rm = TRUE),
n = n()
)
ggplot(delays, aes(x = delay)) +
geom_freqpoly(binwidth = 10)
Manche Flugzeuge haben einen durchschnittlichen delay von
300 Minuten.
Erstellen wir einen Scatterplot mit Anzahl an Flügen vs. average
delay:
ggplot(delays, aes(x = n, y = delay)) +
geom_point(alpha = 1/10)
Gruppen mit den kleinsten Anzahlen an Beobachtungen wollen wir rausfiltern, sodass mehr Pattern und weniger extreme Variationen in kleinen Gruppen angezeigt werden.
delays |>
filter(n > 25) |>
ggplot(aes(x = n, y = delay)) +
geom_point(alpha = 1/10) +
geom_smooth(se = FALSE)
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
Beispiel_2 Baseball.
Aus dem Lahman Paket: Anteil an Versuchen, bei denen der Ball getroffen wurde vs. Anzahl an Versuche.
batters <- Lahman::Batting |>
group_by(playerID) |>
summarize(
perf = sum(H, na.rm = TRUE) / sum(AB, na.rm = TRUE),
n = sum(AB, na.rm = TRUE)
)
batters
## # A tibble: 20,469 × 3
## playerID perf n
## <chr> <dbl> <int>
## 1 aardsda01 0 4
## 2 aaronha01 0.305 12364
## 3 aaronto01 0.229 944
## 4 aasedo01 0 5
## 5 abadan01 0.0952 21
## 6 abadfe01 0.111 9
## 7 abadijo01 0.224 49
## 8 abbated01 0.254 3044
## 9 abbeybe01 0.169 225
## 10 abbeych01 0.281 1756
## # ℹ 20,459 more rows
Zwei Muster sind zu erkennen: mehr Datenpunkte heißt weniger
Variation. Und eine positive Korrelation zwischen perf und
n ist zu erkennen. Logisch, da der beste Batter auch die
meisten Möglichkeiten kriegen soll.
batters |>
filter(n > 100) |>
ggplot(aes(x = n, y = perf)) +
geom_point(alpha = 1 / 10) +
geom_smooth(se = FALSE)
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
Sortierst du naiv nach desc(ba) (batting
average), sind oben die glücklichsten, nicht die besten Spieler zu
finden.
batters |>
arrange(desc(perf))
## # A tibble: 20,469 × 3
## playerID perf n
## <chr> <dbl> <int>
## 1 abramge01 1 1
## 2 alberan01 1 1
## 3 banisje01 1 1
## 4 bartocl01 1 1
## 5 bassdo01 1 1
## 6 birasst01 1 2
## 7 bruneju01 1 1
## 8 burnscb01 1 1
## 9 cammaer01 1 1
## 10 campsh01 1 1
## # ℹ 20,459 more rows
Die Pipe |> ist ein mächtiges Werkzeug, die eine
Reihe von Operationen ausdrückt, die ein Objekt transformieren. Bekannt
ist vielleicht der Vorgänger %>%.
Für den keyboard shortcut Ctrl + Shift + M, gehe in Options, Editing und klicke Use native pipe operator an.
Jedes dplyr verb ist sehr simpel, jedoch erfordert die Lösung komplexer Probleme eine Kombination vieler Verben.
flights |>
filter(!is.na(arr_delay), !is.na(tailnum)) |>
group_by(tailnum) |>
summarize(
delay = mean(arr_delay, na.rm = TRUE),
n = n()
)
## # A tibble: 4,037 × 3
## tailnum delay n
## <chr> <dbl> <int>
## 1 D942DN 31.5 4
## 2 N0EGMQ 9.98 352
## 3 N10156 12.7 145
## 4 N102UW 2.94 48
## 5 N103US -6.93 46
## 6 N104UW 1.80 46
## 7 N10575 20.7 269
## 8 N105UW -0.267 45
## 9 N107US -5.73 41
## 10 N108UW -1.25 60
## # ℹ 4,027 more rows
Die Verben stehen am Beginn jeder Zeile:
flights Daten, dann filter, dann gruppieren, dann
zusammenfassen.
Ohne Pipe:
summarize(
group_by(
filter(
flights,
!is.na(arr_delay), !is.na(tailnum)
),
tailnum
),
delay = mean(arr_delay, na.rm = TRUE
),
n = n()
)
## # A tibble: 4,037 × 3
## tailnum delay n
## <chr> <dbl> <int>
## 1 D942DN 31.5 4
## 2 N0EGMQ 9.98 352
## 3 N10156 12.7 145
## 4 N102UW 2.94 48
## 5 N103US -6.93 46
## 6 N104UW 1.80 46
## 7 N10575 20.7 269
## 8 N105UW -0.267 45
## 9 N107US -5.73 41
## 10 N108UW -1.25 60
## # ℹ 4,027 more rows
%>% PipeIm magrittr Paket wird die %>% Pipe
angeboten. Diese Pipe kannst du benutzen, wenn das tidyverse Paket
geladen ist.
library(tidyverse)
mtcars %>%
group_by(cyl) %>%
summarize(n = n())
## # A tibble: 3 × 2
## cyl n
## <dbl> <int>
## 1 4 11
## 2 6 7
## 3 8 14
|> vs. %>%Die Pipe übergibt das Objekt zu ihrer linken Seite als erstes
Argument auf die rechte Seite. %>% erlaubt es die
Platzierung zu tauschen:
x %>% f(1) ist äquivalent zu
f(x, 1), aber x %>% f(1, .) ist äquivalent
zu f(1, x).
Bei der “neuen Pipe” muss das Argument genannt werden.
x |> f(1, y = _) ist äquivalent zu
f(1, y = x).
Mit %>% kannst du . auf der linken
Seite von Operatoren wie $, [[ oder
[ benutzen, so dass du z.B. eine einzelne Spalte
extrahieren kannst: mtcars %>% .$cyl.
Du kannst sogar zwei mal ersetzen:
df %>% {split(.$x, .$y)} ist äquivalent zu
split(df$x, df$y).
Für das Extrahieren einer Spalte benutze statt
pull(mtcars, cyl) die pull Funktion
dplyr::pull():
mtcars |> pull(cyl)
## [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4
%>% erlaubt es dir bei Funktionen die Klamern
wegzulassen, wenn es keine Argumente gibt:
mtcars[,1] %>% mean funktioniert,
mtcars[,1] |> mean aber nicht.|> vs. +Da ggplot2 vor der Pipe entwickelt wurde, ist der
Übergang von |> zu + notwendig:
ggplot2::diamonds |>
count(cut, clarity) |>
ggplot(aes(x = clarity, y = cut, fill = n)) +
geom_tile()
Vor den zwei Doppelpunkten steht das Paket aus dem der Datensatz kommt! Gleiche Ergebnisse liefert übrigens:
aa<-count(ggplot2::diamonds, cut, clarity)
ggplot(aa, aes(x = clarity, y = cut, fill = n)) +
geom_tile()
Ohne guten Code style kannst du arbeiten, mit ist es aber
wesentlich einfacher. Es gibt sogar Pakete mit denen du deinen Code
aufhübschen kannst. Zum Beispiel das styler Paket. Nach
der Installation benutze Cmd + Shift + P und gebe styler
ein, um alle Shortcuts zu sehen.
Kleinbuchstaben, Zahlen und Trennungen durch _ werden
als guter Style angesehen.
# Strebe nach:
short_flights <- flights |> filter(air_time < 60)
# Vermeide:
SHORTFLIGHTS <- flights |> filter(air_time < 60)
Lange, beschreibende Namen sind einfach zu verstehen. Kurze sind schneller zu tippen, aber auch eventuell später schwer zu verstehen.
Lücken bei mathematischen Operatoren erleichtern das Lesen
(+, -, ==, <).
Nicht bei ^, aber um Zuweisungen. Keine Lücken stehen nach
oder vor Klammern, aber nach dem ,.
# bitte
z <- (a + b)^2 / d
# nicht
z<-( a + b ) ^ 2/d
# bitte
mean(x, na.rm = TRUE)
# nicht
mean (x ,na.rm=TRUE)
Extra Lücken einzufügen, so dass es der Lesbarkeit und Harmonie dient, ist durchaus wünschenswert.
flights |>
mutate(
speed = air_time / distance,
dep_hour = dep_time %/% 100,
dep_minute = dep_time %% 100
)
Pipes sollten immer das letzte Element einer Zeile sein.
# bitte
flights |>
filter(!is.na(arr_delay), !is.na(tailnum)) |>
count(dest)
# nicht
flights|>filter(!is.na(arr_delay), !is.na(tailnum))|>count(dest)
Hat die piping function Argumente wie mutate()
oder summarize(), so kommt jedes Argument in eine neue
Zeile.
# bitte
flights |>
group_by(tailnum) |>
summarize(
delay = mean(arr_delay, na.rm = TRUE),
n = n()
)
# nicht
flights |>
group_by(
tailnum
) |>
summarize(delay = mean(arr_delay, na.rm = TRUE), n = n())
Nach der Zeile der Pipe wird zwei Leerzeichen eingerückt.
Bekommt jedes Argument eine eigene Zeile wird wieder eingerückt. Die
) bekommt wieder eine eigene Zeile, auf Höhe des
Funktionsnamens.
# bitte
flights |>
group_by(tailnum) |>
summarize(
delay = mean(arr_delay, na.rm = TRUE),
n = n()
)
# nicht
flights|>
group_by(tailnum) |>
summarize(
delay = mean(arr_delay, na.rm = TRUE),
n = n()
)
flights|>
group_by(tailnum) |>
summarize(
delay = mean(arr_delay, na.rm = TRUE),
n = n()
)
Manchmal passt der komplette Befehl in eine Zeile, jedoch wächst erfahrungsgemäß die Zeile schnell an.
# alles passt kompakt in eine Zeile
df |> mutate(y = x + 1)
# vier mal so viel Zeilen, jedoch einfach zu verlängern
# mehr Variable und Schritte in der Zukunft.
df |>
mutate(
y = x + 1
)
Vermeide es zu lange Pipes von mehr als 10-15 Zeilen zu schreiben. Breche sie runter in Unteraufgaben und verpasse ihnen günstige Namen. Wichtig.
Was für |> bei der Pipe gilt, gilt natürlich
auch für + bei ggplot2.
flights |>
group_by(month) |>
summarize(
delay = mean(arr_delay, na.rm = TRUE)
) |>
ggplot(aes(x = month, y = delay)) +
geom_point() +
geom_line()
Jedes Argument kommt in ihre eigene Zeile.
flights |>
group_by(dest) |>
summarize(
distance = mean(distance),
speed = mean(air_time / distance, na.rm = TRUE)
) |>
ggplot(aes(x = distance, y = speed)) +
geom_smooth(
method = "loess",
span = 0.5,
se = FALSE,
color = "white",
size = 4
) +
geom_point()
Wird dein Skript länger, so trenne es in kleinere Happen. Verpasse Kommentare und hebe sie optisch hervor.
# Load data --------------------------------------
# Plot data --------------------------------------
Mit Cmd + Shift + R werden sie unten links im Editor angezeigt, in einer drop-down Liste.
Mit den von R bereitgestellten Daten zu arbeiten hilft ungemein, aber für eigene Projekte wollen wir natürlich auch mal eigene Daten importieren.
Dazu brauchen wit das readr Paket, welches Teil des tidyverse ist.
library(tidyverse)
Zu Beginn konzentrieren wir uns auf eine .csv Datei:
In der ersten Zeile stehen für gewöhnlich die Spaltennamen, danach
kommen die Daten.
#> Student ID, Full Name,Age
#> 1,Sunil Huffmann,4
#> 2,Baclay Lynn,5
#> 3,Peter Pan,7
#> 4,Leon Leonidas,
#> 5,Marion Farre,five
#> 6,Attila Attilon,6
Diese Daten können wir aus der Datei einlesen mit
read_csv(). Zwei Spalten haben wir noch hinzugefügt
students <- read_csv("data/students.csv")
## Rows: 6 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (4): Full Name, favourite.food, mealPlan, AGE
## dbl (1): Student ID
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Bei Ausgabe wird die Anzahl der Zeilen und Spalten genannt, das Trennzeichen und die Spalten-Spezifikationen: Namen und Datentyp.
Nach dem Einlesen werden die Daten für gewöhnlich transformiert, so
dass wir mit ihnen einfacher arbeiten können. In der
favourite.food Spalte haben wir den
Character String N/A, den wir in ein
“richtiges” NA, not available, transformieren wollen.
students <- read_csv("data/students.csv", na = c("N/A", ""))
## Rows: 6 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (4): Full Name, favourite.food, mealPlan, AGE
## dbl (1): Student ID
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
students
## # A tibble: 6 × 5
## `Student ID` `Full Name` favourite.food mealPlan AGE
## <dbl> <chr> <chr> <chr> <chr>
## 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
## 2 2 Baclay Lynn French Fries Lunch only 5
## 3 3 Peter Pan <NA> Breakfast and Lunch 7
## 4 4 Leon Leonidas Anchovies Lunch only <NA>
## 5 5 Marion Farre Pizza Breakfast and Lunch five
## 6 6 Attila Attilon Ice cream Lunch only 6
Student ID und Full Name sind umgeben von
Backticks. Das passiert, weil es Leerzeichen in den
Variablennamen gibt, so dass die Namensregeln für Variablen gebrochen
werden. Wir können natürlich die Variablennamen umbenennen.
students |>
rename(
student_id = `Student ID`,
full_name = `Full Name`
)
## # A tibble: 6 × 5
## student_id full_name favourite.food mealPlan AGE
## <dbl> <chr> <chr> <chr> <chr>
## 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
## 2 2 Baclay Lynn French Fries Lunch only 5
## 3 3 Peter Pan <NA> Breakfast and Lunch 7
## 4 4 Leon Leonidas Anchovies Lunch only <NA>
## 5 5 Marion Farre Pizza Breakfast and Lunch five
## 6 6 Attila Attilon Ice cream Lunch only 6
Eine Alternatie ist es janitor::clean_names() zu
benutzen, so dass heuristisch alle Namen in angenehme Formate gebracht
werden.
library(janitor)
## Warning: Paket 'janitor' wurde unter R Version 4.2.3 erstellt
##
## Attache Paket: 'janitor'
## Die folgenden Objekte sind maskiert von 'package:stats':
##
## chisq.test, fisher.test
students |> janitor::clean_names()
## # A tibble: 6 × 5
## student_id full_name favourite_food meal_plan age
## <dbl> <chr> <chr> <chr> <chr>
## 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
## 2 2 Baclay Lynn French Fries Lunch only 5
## 3 3 Peter Pan <NA> Breakfast and Lunch 7
## 4 4 Leon Leonidas Anchovies Lunch only <NA>
## 5 5 Marion Farre Pizza Breakfast and Lunch five
## 6 6 Attila Attilon Ice cream Lunch only 6
Eine andere gewöhnliche Aufgabe ist es den Variablentyp zu bedenken.
Zum Beispiel ist meal_type eine kategoriale Variable mit
einer bekannten Menge an möglichen Werten, die in R als Faktoren
dargestellt werden sollten.
students |>
janitor::clean_names() |>
mutate(
meal_plan = factor(meal_plan)
)
## # A tibble: 6 × 5
## student_id full_name favourite_food meal_plan age
## <dbl> <chr> <chr> <fct> <chr>
## 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
## 2 2 Baclay Lynn French Fries Lunch only 5
## 3 3 Peter Pan <NA> Breakfast and Lunch 7
## 4 4 Leon Leonidas Anchovies Lunch only <NA>
## 5 5 Marion Farre Pizza Breakfast and Lunch five
## 6 6 Attila Attilon Ice cream Lunch only 6
Die Werte sind gleich geblieben, nur der Typ der Variablen (unterhalb
der Namen) hat sich von Character <chr> zu
Faktor <fct> geändert.
Als nächstes wollen wir noch die age Spalte reparieren.
Sein Typ ist Character, weil in einer Zeile five,
statt 5 steht.
students <- students |>
janitor::clean_names() |>
mutate(
meal_plan = factor(meal_plan),
age = parse_number(if_else(age == "five", "5", age))
)
students
## # A tibble: 6 × 5
## student_id full_name favourite_food meal_plan age
## <dbl> <chr> <chr> <fct> <dbl>
## 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
## 2 2 Baclay Lynn French Fries Lunch only 5
## 3 3 Peter Pan <NA> Breakfast and Lunch 7
## 4 4 Leon Leonidas Anchovies Lunch only NA
## 5 5 Marion Farre Pizza Breakfast and Lunch 5
## 6 6 Attila Attilon Ice cream Lunch only 6
Ein Trick am Anfang: read_csv() kann in einem
String kreierte csv Dateien lesen:
read_csv(
"a,b,c
1,2,3
4,5,6"
)
## Rows: 2 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (3): a, b, c
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 2 × 3
## a b c
## <dbl> <dbl> <dbl>
## 1 1 2 3
## 2 4 5 6
Die erste Zeile wird für die Variablen benutzt. Manchmal finden wir
noch davor Metadaten, die du durch skip = n weglassen
kannst. In diesem Fall die ersten n Zeilen. Oder benutze Kommentare wie
folgt:
read_csv(
"The first line of metadata
The second line of metadata
x,y,z
1,2,3",
skip = 2
)
## Rows: 1 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (3): x, y, z
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 1 × 3
## x y z
## <dbl> <dbl> <dbl>
## 1 1 2 3
read_csv(
"# A comment I want to skip
x,y,z
1,2,3",
comment = "#"
)
## Rows: 1 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (3): x, y, z
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 1 × 3
## x y z
## <dbl> <dbl> <dbl>
## 1 1 2 3
Manchmal haben Daten keine Spaltennamen. Durch
col_names = FALSE teilst du dies R mit, so dass die erste
Zeile nicht als Kopf benutzt wird, sondern R automatisch Spaltennamen
von X1 bis Xn ausgibt.
read_csv(
"1,2,3
4,5,6",
col_names = FALSE
)
## Rows: 2 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (3): X1, X2, X3
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 2 × 3
## X1 X2 X3
## <dbl> <dbl> <dbl>
## 1 1 2 3
## 2 4 5 6
Einen Character Vektor mit Spaltennamen kannst du natürlich auch an die Tabelle übergeben.
read_csv(
"1,2,3
4,5,6",
col_names = c("x", "y", "z")
)
## Rows: 2 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (3): x, y, z
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 2 × 3
## x y z
## <dbl> <dbl> <dbl>
## 1 1 2 3
## 2 4 5 6
Für mehr Informationen, schau in der Dokumentation von
read_csv() nach.
Hast du bis jetzt alles verstanden, sind andere Typen unkompliziert:
read_csv2(), read_tsv(),
read_delim(), read_fwf(),
read_table(), read_log().
Der Typ jeder Variable wird von readr geraten, da eine CSV keine Informationen liefert.
Für das Raten wird eine Heuristik von readr genutzt. Es
geht die Reihen durch:
Gibt es F, T, FALSE,
TRUE? Wenn ja, dann ist der Typ logical.
Gibt es Zahlen? Dann number.
Matcht es dem ISO8601 Standard? Dann date oder date-time.
Ansonsten muss es wohl ein String sein.
read_csv("
logical,numeric,date,string
TRUE,1,2021-01-15,abc
false,4.5,2021-02-15,def
T,Inf,2021-02-16,ghi"
)
## Rows: 3 Columns: 4
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): string
## dbl (1): numeric
## lgl (1): logical
## date (1): date
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 3 × 4
## logical numeric date string
## <lgl> <dbl> <date> <chr>
## 1 TRUE 1 2021-01-15 abc
## 2 FALSE 4.5 2021-02-15 def
## 3 TRUE Inf 2021-02-16 ghi
Bei einem “netten” Datensatz funktioniert es. Ansonsten nicht.
Erscheinen unerwartete Werte, so erhalten wir oft einen
Character als Datentyp. Oft ist ein NA dafür
verantwortlich. Schauen wir uns ein einfaches Problem an.
csv <- "
x
10
.
20
30"
Einlesen ergibt:
df <- read_csv(csv)
## Rows: 4 Columns: 1
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): x
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Der Missing Value ist schnell gefunden .. Bei
Tausenden von Reihen fällt das Aufspüren aber schwerer.
Wir können readr sagen, dass x eine numerische
Spalte ist und dann schauen wo es Probleme gibt. Dies geschieht mit dem
col_types Argument, das eine benannte Liste entgegen
nimmt.
df <- read_csv(csv, col_types = list(x = col_double()))
## Warning: One or more parsing issues, call `problems()` on your data frame for details,
## e.g.:
## dat <- vroom(...)
## problems(dat)
Ein Problem wird angezeigt. Mehr können wir herausfinden mit
problems():
problems(df)
## # A tibble: 1 × 5
## row col expected actual file
## <int> <int> <chr> <chr> <chr>
## 1 3 1 a double . C:/Users/nikla/AppData/Local/Temp/RtmpgXCZTo/file…
readr erwartete ein double, aber bekam ein
.. Das lässt vermuten, dass der Dataset . für
Missing Values benutzt. Wir setzen dafür
na = ".".
df <- read_csv(csv, na = ".")
## Rows: 4 Columns: 1
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (1): x
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
readr bietet neun Spaltentypen an:
col_logical() und col_double() liest
logicals und numbers. Sie werden selten gebraucht, da
readr sie meist für uns rät.
col_integer() liest integers (ganze
Zahlen). Sie verbrauchen nur halb so viel Platz wie doubles im
Memory, sind also nicht unwichtig.
col_character() liest Strings.
col_factor(), col_date() und
col_datime() kreieren Faktoren, dates und
date-time.
col_number() ignoriert nicht-numerische
Komponenten.
col_skip() lässt eine Spalte weg.
Überschreiben vom Spaltentyp ist möglich, durch wechseln von
list() zu cols().
csv <- "
x,y,z
1,2,3"
read_csv(csv, col_types = cols(.default = col_character()))
## # A tibble: 1 × 3
## x y z
## <chr> <chr> <chr>
## 1 1 2 3
Oder neu schreiben.
read_csv(csv, col_types = list(x = col_double(), y = col_integer(), z = col_double()))
## # A tibble: 1 × 3
## x y z
## <dbl> <int> <dbl>
## 1 1 2 3
cols_only() liest nur die gewollte Spalte ein.
read_csv(
"x,y,z
1,2,3",
col_types = cols_only(x = col_character())
)
## # A tibble: 1 × 1
## x
## <chr>
## 1 1
Hast du Sales Daten von verschiedenen Monaten in
verschiedenen Dateien, so kannst du sie zusammenfügen mit
read_csv().
sales_files <- c("data/01-sales.csv", "data/02-sales.csv", "data/03-sales.csv")
read_csv(sales_files, id = "file")
Der neue Parameter id fügt eine neue Spalte hinzu, der
die Herkunft der Datei angibt.
Es kann mühselig sein die Namen als Liste zu schreiben, wenn man viele
Dateien hat. Benutze list.files() um die Dateien für dich
zu finden. Ein Muster sollte im Namen vorhanden sein.
sales_files <- list.files("data", pattern = "sales\\.csv$", full.names = TRUE)
sales_files
readr bietet zwei Funktionen, um Daten zu schreiben:
write_csv(), write_tsv(). Als Argumente
brauchst du den Data Frame und den Ort.
write_csv(students, "students.csv")
Die type information geht beim Schreiben als
csv verloren.
students
## # A tibble: 6 × 5
## student_id full_name favourite_food meal_plan age
## <dbl> <chr> <chr> <fct> <dbl>
## 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
## 2 2 Baclay Lynn French Fries Lunch only 5
## 3 3 Peter Pan <NA> Breakfast and Lunch 7
## 4 4 Leon Leonidas Anchovies Lunch only NA
## 5 5 Marion Farre Pizza Breakfast and Lunch 5
## 6 6 Attila Attilon Ice cream Lunch only 6
write_csv(students, "data/students-2.csv")
read_csv("data/students-2.csv")
## Rows: 6 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (3): full_name, favourite_food, meal_plan
## dbl (2): student_id, age
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 6 × 5
## student_id full_name favourite_food meal_plan age
## <dbl> <chr> <chr> <chr> <dbl>
## 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
## 2 2 Baclay Lynn French Fries Lunch only 5
## 3 3 Peter Pan <NA> Breakfast and Lunch 7
## 4 4 Leon Leonidas Anchovies Lunch only NA
## 5 5 Marion Farre Pizza Breakfast and Lunch 5
## 6 6 Attila Attilon Ice cream Lunch only 6
Das macht CSVs ein wenig unzuverlässig für Zwischenergebnisse. Du musst die Spaltenspezifikation jedes mal aufs Neue kreieren. Es gibt zwei Alternativen:
write_rds() und read_rds(). Sie lagern
Daten in R’s binary Format RDS.write_rds(students, "data/students.rds")
read_rds("data/students.rds")
## # A tibble: 6 × 5
## student_id full_name favourite_food meal_plan age
## <dbl> <chr> <chr> <fct> <dbl>
## 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
## 2 2 Baclay Lynn French Fries Lunch only 5
## 3 3 Peter Pan <NA> Breakfast and Lunch 7
## 4 4 Leon Leonidas Anchovies Lunch only NA
## 5 5 Marion Farre Pizza Breakfast and Lunch 5
## 6 6 Attila Attilon Ice cream Lunch only 6
library(arrow)
## Warning: Paket 'arrow' wurde unter R Version 4.2.3 erstellt
##
## Attache Paket: 'arrow'
## Das folgende Objekt ist maskiert 'package:lubridate':
##
## duration
## Das folgende Objekt ist maskiert 'package:utils':
##
## timestamp
write_parquet(students, "data/students.parquet")
read_parquet("data/students.parquet")
## # A tibble: 6 × 5
## student_id full_name favourite_food meal_plan age
## <dbl> <chr> <chr> <fct> <dbl>
## 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
## 2 2 Baclay Lynn French Fries Lunch only 5
## 3 3 Peter Pan <NA> Breakfast and Lunch 7
## 4 4 Leon Leonidas Anchovies Lunch only NA
## 5 5 Marion Farre Pizza Breakfast and Lunch 5
## 6 6 Attila Attilon Ice cream Lunch only 6
Parquets sind schneller als RDS, aber brauchen das Paket.
Manchmal musst du ein tibble() mit der Hand
schreiben.
tibble(
x = c(1, 2, 5),
y = c("h", "m", "g"),
z = c(0.08, 0.83, 0.60)
)
## # A tibble: 3 × 3
## x y z
## <dbl> <chr> <dbl>
## 1 1 h 0.08
## 2 2 m 0.83
## 3 5 g 0.6
Jede Spalte muss natürlich die selbe Länge haben.
tribble() liest die Daten zeilenweise ein. Spaltennamen
starten mit ~, Einträge werden durch Komma getrennt.
tribble(
~x, ~y, ~z,
"h", 1, 0.08,
"m", 2, 0.83,
"g", 5, 0.60,
)
## # A tibble: 3 × 3
## x y z
## <chr> <dbl> <dbl>
## 1 h 1 0.08
## 2 m 2 0.83
## 3 g 5 0.6
In Kapitel 2 haben wir uns mit Plots beschäftigt. In diesem Abschnitt erweitern wir unser Wissen und lernen etwas über die geschichtete Grammatik von Grafiken.
In diesem Abschnitt konzentrieren wir uns natürlich auf
ggplot2. Lade tidyverse.
library(tidyverse)
Unser mpg Datensatz (Autos) hat 234 Zeilen und 11
Spalten:
ggplot2::mpg
## # A tibble: 234 × 11
## manufacturer model displ year cyl trans drv cty hwy fl class
## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
## 1 audi a4 1.8 1999 4 auto… f 18 29 p comp…
## 2 audi a4 1.8 1999 4 manu… f 21 29 p comp…
## 3 audi a4 2 2008 4 manu… f 20 31 p comp…
## 4 audi a4 2 2008 4 auto… f 21 30 p comp…
## 5 audi a4 2.8 1999 6 auto… f 16 26 p comp…
## 6 audi a4 2.8 1999 6 manu… f 18 26 p comp…
## 7 audi a4 3.1 2008 6 auto… f 18 27 p comp…
## 8 audi a4 quattro 1.8 1999 4 manu… 4 18 26 p comp…
## 9 audi a4 quattro 1.8 1999 4 auto… 4 16 25 p comp…
## 10 audi a4 quattro 2 2008 4 manu… 4 20 28 p comp…
## # ℹ 224 more rows
Interessante Variablen für uns sind:
displ: Hubraum in Liter. Numerische
Variable.
hwy: Treibstoffeffizienz in Meilen pro Gallone.
Numerische Variable.
class: Autotyp. Kategoriale Variable.
Mehr zu mpg auf der entsprechenden Hilfsseite über
?mpg.
Zuerst wollen wir die Beziehung zwischen displ und
hwy visualisieren, für die verschiedenen Klassen von Autos.
Ein Scatterplot mit x und y
aes und der kategorialen Variable als color
oder shape überrascht hier weniger.
# Left
ggplot(mpg, aes(x = displ, y = hwy, color = class)) +
geom_point()
# Right
ggplot(mpg, aes(x = displ, y = hwy, shape = class)) +
geom_point()
## Warning: The shape palette can deal with a maximum of 6 discrete values because
## more than 6 becomes difficult to discriminate; you have 7. Consider
## specifying shapes manually if you must have them.
## Warning: Removed 62 rows containing missing values (`geom_point()`).
Wir kriegen zwei Warnungen. Auf der shape Seite können
wir nur 6 verschiedene Symbole als Punkte darstellen, wir haben aber 7.
Eventuell können wir hier manuell auf 7 kommen.
Zusätzlich werden 62 Zeilen vernachlässigt, da Missing
Values vorliegen. Diese sind die SUVs, die nicht geplotted wurden,
da mehr als 6 Formen nicht mögich sind.
Statt Farben unf Formen können wir auch Größe und Transparenz
plotten.
# Left
ggplot(mpg, aes(x = displ, y = hwy, size = class)) +
geom_point()
## Warning: Using size for a discrete variable is not advised.
# Right
ggplot(mpg, aes(x = displ, y = hwy, alpha = class)) +
geom_point()
## Warning: Using alpha for a discrete variable is not advised.
Warnungen erscheinen ebenfalls. Es ist nicht ratsam eine
nicht-ordinale diskrete Variable mit Hilfe einer geordneten
aes (size oder alpha) abzubilden,
da eine Reihenfolge hier gar nicht existiert. Größe und Transparenz
suggerieren dies aber.
Bilden wir ein aes ab, so sorgt ggplot2 für
den Rest. Eine Legende, die die Zuordnung zwischen den Stufen und Werten
erklärt. Diese wird bei uns nicht gebaut, dafür Achsen mit Markierungen
und Werten. Du kannst aes Eigenschaften unseres
geom manuell setzen, wie z.B. blaue Punkte.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(color = "blue")
Ein aesthetic kannst du setzen durch den Namen als Argument
der geom Funktion, als Wert musst du etwas sinnvolles
finden:
# Left
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point()
# Right
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Beide Plots nutzen verschiedene geometrische Objekte (geom), um die Daten zu repräsentieren:
point geom (Punkte) vs. smooth geom (Linie).
Jede geom Funktion in ggplot2 nimmt ein
mapping Argument, aber nicht jedes aes
funktioniert mit jedem geom. Den shape einer Linie
zu setzen macht keinen Sinn. Wenn du es versuchst, so ignoriert
ggplot2 dieses aes mapping.
ggplot(mpg, aes(x = displ, y = hwy, shape = drv)) +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
ggplot(mpg, aes(x = displ, y = hwy, linetype = drv)) +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Hier sorgt geom_smooth() für drei Linie, basierend auf
ihrem drv Wert (Antrieb). 4 steht für
4-Rad-Antrieb, f Vorderantrieb, r
Heckantrieb.
ggplot(mpg, aes(x = displ, y = hwy, linetype = drv, color = drv)) +
geom_smooth() +
geom_point()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Dieser Plot enthält zwei geoms im selben Graphen. Viele
geoms, wie geom_smooth() geben multiple Reihen
von Daten aus. Für sie kannst du group aes zu
kategorialen Variablen setzen, um multiple Objekte zu zeichnen.
ggplot2 zeichnet aber immer ein separates Objekt für jeden
Wert der gruppierenden Variable. Mach davon gebrauch, da der
group aes keine Legende liefert.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_smooth(aes(group = drv))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_smooth(aes(color = drv), show.legend = FALSE)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Wenn du Zuordnungen in einer geom Funktion platzierst,
behandelt ggplot2 sie als lokale Mappings. Es
erweitert oder überschreibt die globalen Mappings für diese
Ebene (layer). So kann man verschiedene aes in
verschiedenen Ebenen anzeigen.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Diese Idee kannst du genauso benutzen, um verschiedene Daten für jede
Ebene zu benutzen. Rote Punkte und Kreise zeigen Zweisitzer-Autos an.
Das lokale Daten-Argument in geom_smooth() überschreibt das
globale in ggplot2 für diese eine Ebene.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
geom_point(
data = mpg |> filter(class == "2seater"),
color = "red"
) +
geom_point(
data = mpg |> filter(class == "2seater"),
shape = "circle open", size = 3, color = "red"
)
Geoms sind die wichtigen Fundamente bei der Erstellung von ggplots. Der Look des Plots kann komplett durch die Wahl dieser bestimmt und verändert werden. Unten erkennen wir, dass es zwei Ausreißer gibt, die Verteilung zweigipflig und rechtsschief ist. Dafür betrachten wir uns verschiedene Plots.
# Left
ggplot(mpg, aes(x = hwy)) +
geom_histogram(binwidth = 2)
# Middle
ggplot(mpg, aes(x = hwy)) +
geom_density()
# Right
ggplot(mpg, aes(x = hwy)) +
geom_boxplot()
ggplot2 bietet mehr als 40 geoms an, es gibt
aber natürlich noch mehr Möglichkeiten Link.
Die Dichte mithilfe von ridgeline plots zu erstellen ist
sehr chic. fill und color machen die Dichten
bunt und alpha transparent.
library(ggridges)
## Warning: Paket 'ggridges' wurde unter R Version 4.2.3 erstellt
ggplot(mpg, aes(x = hwy, y = drv, fill = drv, color = drv)) +
geom_density_ridges(alpha = 0.5, show.legend = FALSE)
## Picking joint bandwidth of 1.28
#> Picking joint bandwidth of 1.28
Schon kennengelernt haben wir facet_wrap(), das Plots in
Unterplots unterteilt, basierend auf einer kategorialen Variable.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
facet_wrap(~cyl)
Um deinen Plot weiter zu unterteilen auf zwei Variablen, benutze
facet_grid().
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
facet_grid(drv ~ cyl)
cyl und drv sind kategoriale Variablen. Für
beide werden immer wieder dieselben Achsen x und y gewählt. Die Skala
bleibt unverändert. Setzt du das scales Argument in der
facet Funktion auf "free", so werden automatisch
verschiedene Bereiche der Achsen gesetzt.
Weitere Arguemnte sind free_x und free_y für
jeweils nur eine der beiden Achsen.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
facet_grid(drv ~ cyl, scales = "free")
Ein Balkendiagramm mit geom_bar() oder
geom_col() ist schnell erstellt. Der cut
unseres Diamantendatensatzes ist schnell visualisiert.
ggplot(diamonds, aes(x = cut)) +
geom_bar()
Auf der y-Achse wird “count” abgetragen. Diese ist aber
keine Variable in diamonds. Der Algorithmus, der benutzt
wird um neue Werte für jeden Graph zu berechnen, heißt
stat (statistical transformation).
Welches stat ein geom benutzt, kannst du am
default Wert sehen. ?geom_bar zeigt, dass der
default Wert für stat “count” ist.
geom_bar() benutzt also stat_count(), welches
auf derselben Seite wie geom_bar() dokumentiert ist. Im
Abschnitt “Computed Variables” siehst du, dass zwei neue
Variablen berechnet werden: “count” und “prop”. Jedes
geom hat ein default stat und andersrum. Du brauchst
dir also keine Sorgen über die stats zu machen, wenn du
geoms benutzt. Es gibt jedoch drei Gründe, warum du ein
stat explizit benutzen solltest:
cut_frequencies <- tribble(
~cut, ~freq,
"Fair", 1610,
"Good", 4906,
"Very Good", 12082,
"Premium", 13791,
"Ideal", 21551
)
ggplot(cut_frequencies, aes(x = cut, y = freq)) +
geom_bar(stat = "identity")
ggplot(diamonds, aes(x = cut, y = after_stat(prop), group = 1)) +
geom_bar()
stat_summary() fasst die y-Werte zusammen für jeden
x-Wert und gibt Median, Minimum und Maximum aus.ggplot(diamonds) +
stat_summary(
aes(x = cut, y = depth),
fun.min = min,
fun.max = max,
fun = median
)
ggplot2 bietet mehr als 20 stats an. Jedes ist
eine Funkton.
Die Farben der Balken kannst du mit color oder
fill gestalten.
ggplot(diamonds, aes(x = cut, color = cut)) +
geom_bar()
ggplot(diamonds, aes(x = cut, fill = cut)) +
geom_bar()
Was passiert jetzt, wenn du eine weitere Variable hinzufügst? Die
Balken stapeln sich automatisch. Jedes Rechteck repräsentiert eine
Kombination aus cut und clarity.
ggplot(diamonds, aes(x = cut, fill = clarity)) +
geom_bar()
Das Stapeln wird automatisch durch die position
adjustment ausgeführt. Du kannst es auch verhindern, indem du
position auf "identity", "dodge",
oder "fill" setzt.
position = "identity" platziert jedes Objekt genau an
der anfallenden Position ohne es zu stapeln. Für Balken ist das
natürlich nicht sehr sinnvoll, da viele Überlappungen entstehen. Wir
können die Balken transparent machen (halb, ganz), um das Diagramm ein
wenig anschaulicher zu gestalten. alpha und
fill = NA helfen.ggplot(diamonds, aes(x = cut, fill = clarity)) +
geom_bar(alpha = 1/5, position = "identity")
ggplot(diamonds, aes(x = cut, color = clarity)) +
geom_bar(fill = NA, position = "identity")
position = "fill" arbeitet wie das Stapeln, nur dass
jedes Set von Balken die gleiche Höhe hat. So lassen sich Anteile
leichter vergleichen.ggplot(diamonds, aes(x = cut, fill = clarity)) +
geom_bar(position = "fill")
position = "dodge" platziert die überlappenden Objekte
direkt nebeneinander. So lassen sich die individuellen Werte leichter
vergleichen.ggplot(diamonds, aes(x = cut, fill = clarity)) +
geom_bar(position = "dodge")
Ein weiteres nützliches Adjustment lässt sich für Scatterplots finden.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point()
Hier haben wir 126 Punkte, obwohl 234 Beobachtungen im Datensatz
existieren. Warum?
Weil sich viele Punkte überlappen. Das nennt man
overplotting. Die Verteilung der Daten ist so schwerer
zu erkennen. Das lässt sich durch position = "jitter"
vermeiden. Die Punkte werden nahe beieinander platziert.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(position = "jitter")
Das kartesische Koordinatensystem mit x-Achse und y-Achse ist bekannt. Es gibt aber noch zwei weitere Koordinatensysteme.
coord_quickmap() setzt das Abbildungsverhältnis für
Karten. Das ist bei Geodaten mit ggplot2 wichtig. Hier wird
nichts verzerrt, die Proportionen bleiben maßstabsgetreu.nz <- map_data("nz")
ggplot(nz, aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "white", color = "black")
ggplot(nz, aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "white", color = "black") +
coord_quickmap()
coord_polar() benutzt Polarkoordianten. Auch eine
schöne Visualisierung (Coxcomb Chart).bar <- ggplot(data = diamonds) +
geom_bar(
mapping = aes(x = cut, fill = cut),
show.legend = FALSE,
width = 1
) +
theme(aspect.ratio = 1) +
labs(x = NULL, y = NULL)
bar + coord_flip()
bar + coord_polar()
Ein Template für Grafiken mit Position Adjustments, stats, Koordinatensystem und Faceting sieht jetzt so aus.
ggplot(data = <DATA>) +
<GEOM_FUNCTION>(
mapping = aes(<MAPPINGS>),
stat = <STAT>,
position = <POSITION>
) +
<COORDINATE_FUNCTION> +
<FACET_FUNCTION>
Unser neues Template nimmt sieben Parameter. In der Praxis musst du natürlich selten alle sieben Parameter nennen für einen Graphen, da defaults vorhanden sind.
Am Anfang steht der Datensatz, den du so transformierst, dass er die Informationen enthält, die du brauchst (mit stat). Wähle ein geometrisches (geom) Objekt, um jede Beobachtung darzustellen. Benutze die aes, um Variablen darzustellen. Dann folgt das Mapping, du wählst ein passendes Koordinatensystem für deine geoms, wählst die Achsen weise.
Du hast einen Graphen, kannst Position Adjustments vornehmen, den Grapen splitten in Subplots. Mehrere Schichten können hinzugefügt werden, wobei jede Schicht einen Dataset, geom, Mappings, stat und Position Adjustment benutzt. Mit diesem Verfahren kannst du Hunderte von Plots bauen.
In diesem Kapitel werden Daten visualisiert, und transformiert. EDA ist ein wichtiger Teil der Datenanalyse. Data Cleaning gehört hier genauso zu, wie die Modelling.
Alles was wir bisher mithilfe von dplyr und
ggplot2 kennengelernt haben, kommt hier zur Anwendung.
library(tidyverse)
Das Ziel der EDA ist es ein Verständnis deiner Daten zu entwickeln.
Durch Fragestellungen wird dein Interesse auf einen bestimmten Bereich
gelenkt, so dass diese dir helfen die passenden Graphiken, Modelle und
Transformstionen zu wählen.
Es gibt nicht DIE passende Frage, aber Fragen über die Streuung in den
Variablen und Kovarianz zwischen den Daten zu stellen, ist nie
verkehrt.
Jede Messung stetiger oder diskreter Variablen bringt meist eine gewisse Streuung mit sich. Gewicht, Größe und Alter in der Bevölkerung sind nämlich nicht konstant. Jede Variable hat ihr eigenes Muster der Streuung. Um dieses zu verstehen, hilft es erst einmal es zu visualisieren.
Wir beginnen mit der Visualisierung von Gewichten
(carat). Unser Diamantendatensatz dürfte mittlerweile
bekannt sein. 54 000 Diamanten liegen vor und carat ist
hierbei eine numerische Variable. Wir können sie somit im Histogramm
darstellen.
ggplot(diamonds, aes(x = carat)) +
geom_histogram(binwidth = 0.5)
Im Balkendiagramm und Histogramm stehen hohe Balken für gewöhnliche und kurze Balken für seltenen Werte. Liegt kein Balken vor, so fehlen hier die Ausprägungen.
Bei den Diamanten fragt man sich, wieso es Brüche gibt und warum ganze Werte überproportional häufig vorkommen. Warum ist die Verteilung der Peaks rechtsschief?
smaller <- diamonds |>
filter(carat < 3)
ggplot(smaller, aes(x = carat)) +
geom_histogram(binwidth = 0.01)
Die Länge von 272 Eruptionen eines Geysirs zeigt ein interessantes Muster auf.
ggplot(faithful, aes(x = eruptions)) +
geom_histogram(binwidth = 0.25)
Viele Fragen haben mit der Abhängigkeit von zwei Variablen zu tun. Eine erklärt das Verhalten der anderen.
Ausreißer sind unübliche Beobachtungen. Sie scheinen nicht in das Muster der Verteilung zu passen. Manchmal sind sie Eintragungsfehler, andere Male geben sie neue Erkenntnisse. Im Histogramm sind sie manchmal schwer zu entdecken.
ggplot(diamonds, aes(x = y)) +
geom_histogram(binwidth = 0.5)
Um das Entdecken leichter zu machen, müssen wir ein wenig in die Daten reinzoomen.
ggplot(diamonds, aes(x = y)) +
geom_histogram(binwidth = 0.5) +
coord_cartesian(ylim = c(0, 50))
coord_cartesian() hat auch ein xlim()
Argument, so dass du in den Bereich der x-Achse reinzoomen kannst.
ggplot2 hat auch xlim() und
ylim() Funktionen, die Daten außerhalb der Grenzen
entsorgen.
So entdecken wir drei unübliche Werte: 0, ~30, ~60.
unusual <- diamonds |>
filter(y < 3 | y > 20) |>
select(price, x, y, z) |>
arrange(y)
unusual
## # A tibble: 9 × 4
## price x y z
## <int> <dbl> <dbl> <dbl>
## 1 5139 0 0 0
## 2 6381 0 0 0
## 3 12800 0 0 0
## 4 15686 0 0 0
## 5 18034 0 0 0
## 6 2130 0 0 0
## 7 2130 0 0 0
## 8 2075 5.15 31.8 5.12
## 9 12210 8.09 58.9 8.06
Die y Variable misst einer der drei Dimensionen des
Diamanten in mm. Eine Breite von 0 mm ist natürlich nicht möglich, also
falsch. Die großen Diamanten müssen auch falsch sein, da die Kosten
nicht mit der Größe korrelieren.
Die Analyse kann mit und ohne Ausreißer gefahren werden. Hier können sie
rausgenommen werden. Verändern sie das Bild der Daten; so meist nicht.
Welche Gründe gibt es? Ein Ausschluss muss gerechtfertigt sein.
Bei der Analyse von unüblichen Werten hast du zwei Möglichkeiten.
diamonds2 <- diamonds |>
filter(between(y, 3, 20))
Es ist nicht zu empfehlen, da ganze Beobachtungsreihen ausgeschlossen werden, statt einer defekten. Auch kann dann dein Datensatz zu stark reduziert sein.
mutate(). Der
ifelse() Befehl hilft hier. if_else() ist
nicht ifelse(). Da bei if_else() die
erstellten Variablen von verschiedenem Datentyp sind, müssen wir die
NA auch zum Datentyp double transformieren. Bei
ifelse() ist dies nicht nötig.diamonds2 <- diamonds |>
mutate(y = if_else(y < 3 | y > 20, NA_real_, y))
ifelse() hat drei Argumente. Das erste test
sollte ein logischer Vektor sein. Alternativ benutze
case_when().
Erstellst du einen Plot mit NA’s, so warnt dich
ggplot2, dass sie entfernt wurden.
ggplot(diamonds2, aes(x = x, y = y)) +
geom_point()
## Warning: Removed 9 rows containing missing values (`geom_point()`).
#> Warning: Removed 9 rows containing missing values (`geom_point()`).
Diese Warnung kann mit na.rm = TRUE unterdrückt
werden.
ggplot(diamonds2, aes(x = x, y = y)) +
geom_point(na.rm = TRUE)
In einem anderen Beispiel wollen wir herausfinden, inwiefern sich
Beobachtugen unterscheiden, bei denen Werte vorliegen im Gegensatz zu
Beobachtungen, bei denen Missing Values vorhanden sind.
nycflights13::flights hat in dep_time
fehlenden Werte, sodass der Flug wahrscheinlich gecancelt wurde.
Vergleichen wir also die planmäßigen Abflugzeiten der Flüge bzw.
abgesagten Flüge.
nycflights13::flights |>
mutate(
cancelled = is.na(dep_time),
sched_hour = sched_dep_time %/% 100,
sched_min = sched_dep_time %% 100,
sched_dep_time = sched_hour + (sched_min / 60)
) |>
ggplot(aes(x = sched_dep_time)) +
geom_freqpoly(aes(color = cancelled), binwidth = 1/4)
Der Vergleich ist schwierig, da hier viel mehr Flüge als abgesagte sind.
Die Variation betrachtet eine Variable, die Kovariation beschreit das Verhalten zwischen Variablen. Die Beziehung zwischen zwei oder mehr Variablen zu visualisieren, hilft bei der Analyse natürlich enorm.
Zum Beispiel der Preis eines Diamanten wird mit der Qualität
verglichen (cut).
ggplot(diamonds, aes(x = price)) +
geom_freqpoly(aes(color = cut), binwidth = 500, size = 0.75)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
#> Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
#> ℹ Please use `linewidth` instead.
Wieder nicht ideal, da es große Unterschiede in den Häufigkeiten gibt.
ggplot(diamonds, aes(x = cut)) +
geom_bar()
Um den Vergleich einfacher zu machen, werden die Werte auf der
y-Achse ausgetauscht. Nehmen wir die Dichte, so ist die Fläche unter dem
Graphen für alle cut Werte immer gleich eins.
ggplot(diamonds, aes(x = price, y = after_stat(density))) +
geom_freqpoly(aes(color = cut), binwidth = 500, size = 0.75)
Da density keine Variable des diamonds
Datensatzes ist, müssen wir es berechnen. Dafür benutzen wir die
after_stat() Funktion.
Das Ergebnis jedoch überrascht. Es sieht so aus, als ob die niedrigste
(fair) Qualität, den höchsten Durchschnittspreis hat.
Ein visuell einfacher Plot, ist der Boxplot.
ggplot(diamonds, aes(x = cut, y = price)) +
geom_boxplot()
Weniger Informationen sehen wir, aber dafür sind die Boxplots
kompakter. Aber auch hier kann an der These festgehalten werden, dass
qualitativ bessere Diamanten günstiger sind. Warum ist das wohl
so?
cut ist hier ein geordneter Faktor. Viele Variablen haben
nicht eine Anordnung, so dass sie neu sortiert werden müssen. Eine
Möglichkeit ist hier die fct_reorder() Funktion.
Im mpg Datensatz z.B. wollen wir die highway
mileage (Fahrleistung auf Autobahn) je nach class
(type of car) darstellen.
ggplot(mpg, aes(x = class, y = hwy)) +
geom_boxplot()
Sortieren wir class neu, basierend aud dem Median von
hwy:
ggplot(mpg,
aes(x = fct_reorder(class, hwy, median), y = hwy)) +
geom_boxplot()
Oder:
ggplot(mpg,
aes(x = hwy, y = fct_reorder(class, hwy, median))) +
geom_boxplot()
Hier musst du für jede Kombination von Levels die Häufigkeiten zählen.
ggplot(diamonds, aes(x = cut, y = color)) +
geom_count()
Die Größe der Kreise zeigt wie viele Beobachtungen in jedes Paar
fallen.
Mit dplyr lassen sich die Counts bestimmen und mit
geom_tile() visualisieren.
diamonds |>
count(color, cut) |>
ggplot(aes(x = color, y = cut)) +
geom_tile(aes(fill = n))
Die Reihen und Spalten lassen sich wieder ordnen und eine Heatmap wird visualisiert.
Kovariation kannst du als Muster in den Punkten sehen. Eine exponentiale Beziehung zwischen Karatgröße und Preis von Diamanten z.B.
ggplot(diamonds, aes(x = carat, y = price)) +
geom_point()
Bei großen Datensätzen werden Scatterplots jedoch
unhandlich, da sich Punkte überlappen: der alpha
aesthetic hilft.
ggplot(diamonds, aes(x = carat, y = price)) +
geom_point(alpha = 1 / 100)
Für große Datensätze kann transparency aber herausfordernd
sein. Wir können aber die Daten gruppieren. Schon kennengelernt haben
wir dafür geom_histogram() und
geom_freqpoly(). Wir haben in eine Dimension gruppiert,
jetzt in zwei Dimensionen (geom_bin2d() und
geom_hex()).
Farben zeigen an, wieviele Punkte in jedes Rechteck fallen. Es wird
zwischen Rechtecken und Sechsecken unterschieden.
ggplot(smaller, aes(x = carat, y = price)) +
geom_bin2d()
# install.packages("hexbin")
ggplot(smaller, aes(x = carat, y = price)) +
geom_hex()
Eine weitere, gute Möglichkeit ist es stetige als kategoriale Variablen zu gruppieren und dann entsprechende Grafiken zu wählen, wie Boxplots.
ggplot(smaller, aes(x = carat, y = price)) +
geom_boxplot(aes(group = cut_width(carat, 0.1)))
cut_width(x, width) unterteilt x in Gruppen
der Breite width. Jedoch erkennen wir nicht wieviele
Beobachtungen in jeden Boxplot fallen, sondern nur die Verteilung. Wir
können jedoch die Breite proportional machen zu der Anzahl der
Beobachtungen, mit varwidth = TRUE.
Ein anderer Ansatz ist es dieselbe Anzahl an Punkten in jeder Gruppe
anzeigen zu lassen. Das macht cut_number():
ggplot(smaller, aes(x = carat, y = price)) +
geom_boxplot(aes(group = cut_number(carat, 20)))
Muster in den Daten geben Hinweise über Zusammenhänge. Wenn ein Zusammenhang zwischen zwei Variablen existiert, so erscheint dieser auch als Muster in den Daten. Wenn du ein Muster in den Daten findest, frag dich:
Das Streudiagramm der Eruptionslänge und Eruptionszeit von einem Geysir zeigt ein Muster auf: längere Wartezeit = längere Eruption. Zwei Cluster sind klar zu erkennen.
ggplot(faithful, aes(x = eruptions, y = waiting)) +
geom_point()
Modelle sind Werkzeuge, um Muster aus den Daten zu ziehen. Gucken wir
uns den Diamantendatensatz an. Der Zusammenhang zwischen
cut und price ist schwer zu verstehen, da
cut und carat und carat und
price miteinander korrelieren. Wir können aber über ein
Model die starke Beziehung zwischen Preis und Karat entfernen.
library(tidymodels)
## Warning: Paket 'tidymodels' wurde unter R Version 4.2.3 erstellt
## ── Attaching packages ────────────────────────────────────── tidymodels 1.1.1 ──
## ✔ broom 1.0.5 ✔ rsample 1.2.0
## ✔ dials 1.2.0 ✔ tune 1.1.2
## ✔ infer 1.0.5 ✔ workflows 1.1.3
## ✔ modeldata 1.2.0 ✔ workflowsets 1.0.1
## ✔ parsnip 1.1.1 ✔ yardstick 1.2.0
## ✔ recipes 1.0.8
## Warning: Paket 'broom' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'dials' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'infer' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'modeldata' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'parsnip' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'recipes' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'rsample' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'tune' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'workflows' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'workflowsets' wurde unter R Version 4.2.3 erstellt
## Warning: Paket 'yardstick' wurde unter R Version 4.2.3 erstellt
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ scales::discard() masks purrr::discard()
## ✖ dplyr::filter() masks stats::filter()
## ✖ recipes::fixed() masks stringr::fixed()
## ✖ dplyr::lag() masks stats::lag()
## ✖ yardstick::spec() masks readr::spec()
## ✖ recipes::step() masks stats::step()
## • Search for functions across packages at https://www.tidymodels.org/find/
diamonds <- diamonds |>
mutate(
log_price = log(price),
log_carat = log(carat)
)
diamonds_fit <- linear_reg() |>
fit(log_price ~ log_carat, data = diamonds)
diamonds_aug <- augment(diamonds_fit, new_data = diamonds) |>
mutate(.resid = exp(.resid))
ggplot(diamonds_aug, aes(x = carat, y = .resid)) +
geom_point()
Jetzt können wir die Beziehng zwischen cut und price herstellen.
ggplot(diamonds_aug, aes(x = cut, y = .resid)) +
geom_boxplot()
Ein weiteres Beispiel:
x <- 1:100
x
## [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
## [19] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
## [37] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
## [55] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
## [73] 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
## [91] 91 92 93 94 95 96 97 98 99 100
z <- c(rep(5, 20), rep(10, 20), rep(-1, 20), rep(-5, 20), rep(-2,20))
y <- rnorm(100) + 1:100 + z
y
## [1] 6.376168 8.099158 7.875622 8.329144 10.052439 10.580695 12.213117
## [8] 14.035885 15.514094 15.677257 16.253343 18.531143 19.262427 18.785907
## [15] 20.616389 20.545670 20.458809 21.850847 24.915792 24.966445 31.655152
## [22] 32.425788 32.161107 34.942453 34.636560 36.132249 36.880930 39.511858
## [29] 38.605595 41.214695 40.797174 41.653744 42.489584 43.841941 45.202105
## [36] 46.599383 46.433016 48.865447 50.417434 48.662236 40.368446 40.361528
## [43] 41.486551 43.667510 42.980121 44.752218 46.386935 46.162512 47.534817
## [50] 47.946187 48.430451 50.039680 53.700632 52.337320 53.393025 56.164311
## [57] 56.447052 56.138467 57.105352 58.612625 57.520323 56.344714 58.278205
## [64] 58.472549 59.087041 61.674406 59.781719 62.992270 63.842671 65.462617
## [71] 64.286213 67.736069 66.884753 68.476007 69.404862 70.238079 72.454414
## [78] 73.107582 74.064977 74.608542 78.382059 79.297975 79.122326 84.055881
## [85] 80.612904 83.402867 82.483709 84.856649 87.865395 88.836661 91.885453
## [92] 90.076806 91.339239 93.700348 92.952374 94.273424 95.510271 95.338530
## [99] 98.990865 98.619574
kat <- c(rep("blau", 20), rep("grün", 20), rep("orange", 20), rep("rot", 20), rep("schwarz", 20))
df <- tibble(x, y, kat)
df
## # A tibble: 100 × 3
## x y kat
## <int> <dbl> <chr>
## 1 1 6.38 blau
## 2 2 8.10 blau
## 3 3 7.88 blau
## 4 4 8.33 blau
## 5 5 10.1 blau
## 6 6 10.6 blau
## 7 7 12.2 blau
## 8 8 14.0 blau
## 9 9 15.5 blau
## 10 10 15.7 blau
## # ℹ 90 more rows
df |>
ggplot(aes(x=x,y=y)) +
geom_point()
d_fit <- linear_reg() |>
fit(y ~ x, data = df)
d_a <- augment(d_fit, new_data = df) |>
mutate(.resid = 1 * .resid)
d_a
## # A tibble: 100 × 5
## .pred .resid x y kat
## <dbl> <dbl> <int> <dbl> <chr>
## 1 9.43 -3.06 1 6.38 blau
## 2 10.3 -2.19 2 8.10 blau
## 3 11.1 -3.27 3 7.88 blau
## 4 12.0 -3.68 4 8.33 blau
## 5 12.9 -2.81 5 10.1 blau
## 6 13.7 -3.14 6 10.6 blau
## 7 14.6 -2.37 7 12.2 blau
## 8 15.4 -1.40 8 14.0 blau
## 9 16.3 -0.781 9 15.5 blau
## 10 17.2 -1.48 10 15.7 blau
## # ℹ 90 more rows
ggplot(d_a, aes(x = x, y = .resid)) +
geom_point() +
geom_vline(xintercept = c(20,40,60,80), lty = 4)
ggplot(d_a, aes(x = kat, y = .resid)) +
geom_boxplot()
In jeder tieferen Analyse werden Massen an Plots erstellt, von denen
die meisten schnell wieder verworfen werden. Hast du deine Schlüsse aus
den Daten gezogen, gilt es fast immer sie zu kommunizieren. Das Problem
ist oft, dass dein Publikum nicht so tief in der Analyse, in den Daten,
in der Fachrichtung, drin steckt wie du. So muss es dein Ziel sein,
deine Plots so selbsterklärend wie möglich zu gestalten. In diesem
Abschitt lernst du ein paar Werkzeuge von ggplot2 kennen,
die dir dabei helfen.
Wieder konzentrieren wir uns hier auf ggplot2 und die
Pakete ggrepel, patchwork und
dplyr.
library(ggrepel)
## Warning: Paket 'ggrepel' wurde unter R Version 4.2.3 erstellt
library(patchwork)
## Warning: Paket 'patchwork' wurde unter R Version 4.2.3 erstellt
Etiketten bzw. Labels helfen natürlich immer eine Grafik anschaulich
zu machen. Sie können mithilfe der labs() Funktion
hinzugefügt werden.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
geom_smooth(se = FALSE) +
labs(title = "Fuel efficiency generally decreases with engine size")
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Die Absicht ist es, die Haupterkenntnis zusammenzufassen. Nur den Plot zu beschreiben sollte man vermeiden. Zwei Möglichkeiten gibt es etwas hinzuzufügen:
subtitle fügt ein weiteres Detail in kleinerer
Schrift darunter hinzu.
caption fügt Text unterhalb der Grafik rechts vom
Plot hinzu, meist die Quelle der Daten.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
geom_smooth(se = FALSE) +
labs(
title = "Fuel efficiency generally decreases with engine size",
subtitle = "Two seaters (sports cars) are an exception because of their light weight",
caption = "Data from fueleconomy.gov"
)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
labs() kannst du auch benutzen, um Achsen- und
Legenden-Titel zu ersetzen.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
geom_smooth(se = FALSE) +
labs(
x = "Engine displacement (L)",
y = "Highway fuel economy (mpg)",
color = "Car type"
)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Mathematische Gleichungen statt Strings können genutzt
werden. Tausche hierfür "" aus durch quote()
und schau dir die Optionen in der Hilfe ?plotmath an:
df <- tibble(
x = 1:10,
y = x ^ 2
)
ggplot(df, aes(x, y)) +
geom_point() +
labs(
x = quote(sum(x[i] ^ 2, i == 1, n)),
y = quote(alpha + beta + frac(delta, theta))
)
Manchmal ist es sinnvoll auch individuelle Beobachtungen oder Gruppen
von Beobachtungen zu labeln. geom_text() ist dabei ähnlich
wie geom_point(), aber es hat einen zusätzlichen
aes: label. Der macht es möglich Text (Labels) zu
deinen Plots hinzuzufügen.
Es gibt zwei mögliche Quellen von Labels. Als erstes könntest du ein
tibble haben, dass Labels anbietet. Im Folgenden schauen
wir uns die Autos mit den größten Hubraum pro Antriebsart an und
speichern die Informationen als neuen Data Frame
label_info. Neue dplyr Funktionen helfen
dabei.
label_info <- mpg |>
group_by(drv) |>
arrange(desc(displ)) |>
slice_head(n = 1) |>
mutate(
drive_type = case_when(
drv == "f" ~ "front-wheel drive",
drv == "r" ~ "rear-wheel drive",
drv == "4" ~ "4-wheel drive"
)
) |>
select(displ, hwy, drv, drive_type)
label_info
## # A tibble: 3 × 4
## # Groups: drv [3]
## displ hwy drv drive_type
## <dbl> <int> <chr> <chr>
## 1 6.5 17 4 4-wheel drive
## 2 5.3 25 f front-wheel drive
## 3 7 24 r rear-wheel drive
Diesen neuen Datensatz benutzen wir um die Labels direkt über den
Plots anzuzeigen. Mit fontface und size können
wir den Look der Labels direkt anpassen. Die Ausrichtung der Labels
erfolgt mit hjust (“left”, “center”, “right”) und
vjust (“top”, “center”, “botom”).
theme(legend.position = "none") unterdrückt die Legende.
Sie brauchen wir auf der rechten Seite jetzt natürlich nicht mehr.
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
geom_point(alpha = 0.3) +
geom_smooth(se = FALSE) +
geom_text(
data = label_info,
aes(x = displ, y = hwy, label = drive_type),
fontface = "bold", size = 5, hjust = "right", vjust = "bottom"
) +
theme(legend.position = "none")
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Durch die Überlappungen ist der beschriftete Plot nicht mehr ganz so
gut zu lesen. Ein Rechteck wird hinter den Text geworfen.
geom_label() hilft. nudge_y hebt die Labels
leicht vor die Punkte des Plots:
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
geom_point(alpha = 0.3) +
geom_smooth(se = FALSE) +
geom_label(
data = label_info,
aes(x = displ, y = hwy, label = drive_type),
fontface = "bold", size = 5, hjust = "right", alpha = 0.5, nudge_y = 2,
) +
theme(legend.position = "none")
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Optimal ist es nicht. geom_label_repel() vom
ggrepel Paket sorgt automatisch für eine Platzierung der
Labels, sodass sie nicht überlappen.
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
geom_point(alpha = 0.3) +
geom_smooth(se = FALSE) +
geom_label_repel(
data = label_info,
aes(x = displ, y = hwy, label = drive_type),
fontface = "bold", size = 5, nudge_y = 2,
) +
theme(legend.position = "none")
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Auch können wir gewisse Punkte optisch hervorheben mit
geom_text_repel(). Hier fügen wir sogar zwei Schichten von
Punkten übereinander, um die Punkte hervorzuheben.
potential_outliers <- mpg |>
filter(hwy > 40 | (hwy > 20 & displ > 5))
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
geom_text_repel(data = potential_outliers, aes(label = model)) +
geom_point(data = potential_outliers, color = "red") +
geom_point(data = potential_outliers, color = "red", size = 3, shape = "circle open")
Ein Label zum Plot kannst du über einen neuen Data Frame hinzufügen. Bestimme dazu die maximalen Werte von x und y und speichere diese Koordinaten. Dann setze die Beschriftung dort ein.
label_info <- mpg |>
summarize(
displ = max(displ),
hwy = max(hwy),
label = "Increasing engine size is \nrelated to decreasing fuel economy."
)
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
geom_text(
data = label_info, aes(label = label),
vjust = "top", hjust = "right"
)
Direkt in die Ecken können wir den Text setzen über +Inf
und -Inf. Da wir die exakten Positionen nicht brauchen,
ertellen wir den Data Frame mithilfe von
tibble().
label_info <- tibble(
displ = Inf,
hwy = Inf,
label = "Increasing engine size is \nrelated to decreasing fuel economy."
)
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
geom_text(data = label_info, aes(label = label), vjust = "top", hjust = "right")
Ohne einen Data Frame funktioniert das Ganze natürlich auch.
annotate() fügt ein geom zu einem Plot hinzu.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
annotate(
geom = "text", x = Inf, y = Inf,
label = "Increasing engine size is \nrelated to decreasing fuel economy.",
vjust = "top", hjust = "right"
)
Wir können ein Label-geom statt eines Text-geoms
benutzen. Ein Segment-geom mit dem arrow Argument
zieht die Aufmerksamkeit auf sich. x und y
aes definieren den Startpunkt, xend und
yend den Endpunkt.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
annotate(
geom = "label", x = 3.5, y = 38,
label = "Increasing engine size is \nrelated to decreasing fuel economy.",
hjust = "left", color = "red"
) +
annotate(
geom = "segment",
x = 3, y = 35, xend = 5, yend = 25, color = "red",
arrow = arrow(type = "closed")
)
"\n" überführt das Label in eine weitere Zeile.
stringr::str_wrap() tut dies automatisch, indem man ihm die
Anzahl der Zeichen vorgibt, pro Zeile.
"Increasing engine size is related to decreasing fuel economy." |>
str_wrap(width = 40) |>
writeLines()
## Increasing engine size is related to
## decreasing fuel economy.
Eine weitere Möglichkeit deinen Plot besser für die Kommunikation zu machen ist die Anpassung der Skalen.
ggplot2 setzt normalerweise die Skalen automatisch für
dich. Wenn du tippst:
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class))
Dann fügt ggplot2 automatisch (hinter dem Vorhang)
default Skalen hinzu.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
scale_x_continuous() +
scale_y_continuous() +
scale_color_discrete()
Die Namensgebung läuft wie folgt ab: scale_ folgt der
Names des aes, dann folgt _, dann der Name der
Skala. Die default Skalen sind nach dem Typ der Variablen
benannt: continuous, discrete, datetime, date. Es gibt jedoch
Gründe die defaults zu überschreiben:
du willst die Parameter der default Skala optimieren. Du kannst die breaks der Achsen wechseln, oder die Labels der Legende.
du willst die Skala komplett ersetzen, und einen anderen Algorithmus verwenden.
Es gibt zwei Argumente, die das Erscheinen der Ticks auf der
Achse und der keys auf der Legende beeinflussen:
breaks und labels. Breaks kontrollieren die
Position der Ticks. Labels kontrollieren die text
labels. Die gewöhnlichste Anwendung von breaks ist das
Überschreiben des default:
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
scale_y_continuous(breaks = seq(15, 40, by = 5))
labels kannst du genauso benutzen (Character
Vektor derselben Länge wie breaks), du kannst aber auch ihn
auf NULL setzen, um die Labels zu unterdrücken.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
scale_x_continuous(labels = NULL) +
scale_y_continuous(labels = NULL)
Das label Argument kannst du mit der labelling
Funktion vom scales Paket paaren, sodass die Formatierung von
Zahlen wie Währungen und Prozenten leichter fällt. Links wird ein
Dollarzeichen gesetzt, rechts durch 1000 geteilt und ein “K” für 1000
angefügt. Die Breaks werden auch individuell gesetzt.
# Left
ggplot(diamonds, aes(x = cut, y = price)) +
geom_boxplot(alpha = 0.05) +
scale_y_continuous(labels = scales::label_dollar())
# Right
ggplot(diamonds, aes(x = cut, y = price)) +
geom_boxplot(alpha = 0.05) +
scale_y_continuous(
labels = scales::label_dollar(scale = 1/1000, suffix = "K"),
breaks = seq(1000, 19000, by = 6000)
)
Eine weitere nützliche label Funktion ist
label_percent():
ggplot(diamonds, aes(x = cut, fill = clarity)) +
geom_bar(position = "fill") +
scale_y_continuous(
name = "Percentage",
labels = scales::label_percent()
)
Hast du wenig Datenpunkte und willst hervorheben, wo die Beobachtung
exakt angefallen ist, benutze breaks.
presidential |>
mutate(id = 33 + row_number()) |>
ggplot(aes(x = start, y = id)) +
geom_point() +
geom_segment(aes(xend = end, yend = id)) +
scale_x_date(name = NULL, breaks = presidential$start, date_labels = "'%y")
Um die Achsen zu optimieren, nutze meist breaks und
labels. Um die Legenden zu kontrollieren, benutze
theme(). Sie kontrollieren den Nicht-Daten-Bereich.
legend.position kontrolliert wo genau die Legende
gezeichnet wird.
base <- ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class))
base + theme(legend.position = "left")
base + theme(legend.position = "top")
base + theme(legend.position = "bottom")
base + theme(legend.position = "right") # the default
Mit legend.position = "none" kannst du die Legende
unterdrücken.
Kontrolliere die Anzahl an Zeilen und die Größe der angezeigten Punkte
in der Legende mithilfe von guides().
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
geom_smooth(se = FALSE) +
theme(legend.position = "bottom") +
guides(color = guide_legend(nrow = 1, override.aes = list(size = 4)))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Oft ist es sinnvoll Transformationen deiner Variable zu plotten. Z.B.
lässt sich so der Zusammenhang zwischen carat und
price besser sehen.
# Left
ggplot(diamonds, aes(x = carat, y = price)) +
geom_bin2d()
# Right
ggplot(diamonds, aes(x = log10(carat), y = log10(price))) +
geom_bin2d()
Der Nachteil ist natürlich, dass die Achsen mit den transformierten Werten gelabelt sind. Das macht die Interpretation des Plots schwierig. Man muss jetzt aber schon genau auf die Achsen schauen.
ggplot(diamonds, aes(x = carat, y = price)) +
geom_bin2d() +
scale_x_log10() +
scale_y_log10()
Die Farbpalette kann für Menschen mit Farbenblindheit angepasst werden.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv))
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv)) +
scale_color_brewer(palette = "Set1")
Ein shape mapping hilft hier natürlich deutlich mehr.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = drv, shape = drv)) +
scale_color_brewer(palette = "Set1")
Wenn eine vordefinierte Zuordnung zwischen Werten und Farben
existiert, benutze scale_color_manual(). Die US Präsidenten
können wir so optisch nach Parteizugehörigkeit hervorheben.
presidential |>
mutate(id = 33 + row_number()) |>
ggplot(aes(x = start, y = id, color = party)) +
geom_point() +
geom_segment(aes(xend = end, yend = id)) +
scale_color_manual(values = c(Republican = "red", Democratic = "blue"))
Für stetige Farbeverläufe benutze scale_color_gradient()
oder scale_fill_gradient(). Für divergierende Skalen
benutze scale_color_gradient2(). So können die negativen
und positiven Werte verschiedenen Farben übergeben werden.
Es gibt drei Möglichkeiten die Plot Limits zu kontrollieren:
xlim und ylim im
coord_cartesian() setzen.In eine Region zoomt man am besten über
coord_cartesian():
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
geom_smooth() +
coord_cartesian(xlim = c(5, 7), ylim = c(10, 30))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
mpg |>
filter(displ >= 5, displ <= 7, hwy >= 10, hwy <= 30) |>
ggplot(aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Die Limits von Plots zu reduzieren ist meist äquivalent zu Subsetting. Es ist aber nicht selten sinnvoll die Limits zu erweitern, sodass zwei oder mehrere Plots besser miteinander verglichen werden können.
suv <- mpg |> filter(class == "suv")
compact <- mpg |> filter(class == "compact")
ggplot(suv, aes(x = displ, y = hwy, color = drv)) +
geom_point()
ggplot(compact, aes(x = displ, y = hwy, color = drv)) +
geom_point()
Um das Problem zu beheben, können wir den ganzen Datensatz auf die
limits trainieren.
x_scale <- scale_x_continuous(limits = range(mpg$displ))
y_scale <- scale_y_continuous(limits = range(mpg$hwy))
col_scale <- scale_color_discrete(limits = unique(mpg$drv))
ggplot(suv, aes(x = displ, y = hwy, color = drv)) +
geom_point() +
x_scale +
y_scale +
col_scale
ggplot(compact, aes(x = displ, y = hwy, color = drv)) +
geom_point() +
x_scale +
y_scale +
col_scale
Du kannst die Nicht-Datenelemente deines Plots mit einem Thema/Theme individuell gestalten.
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class)) +
geom_smooth(se = FALSE) +
theme_bw()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Acht Themes werden von ggplot2 per Default
mitgeliefert. Auch das Paket ggthemes und ähnliche
helfen weiter. Mehr dazu über die Hilfeseiten.
Das Patchwork Paket erlaubt es uns mehrere separate Plots in
derselben Graphik zu vereinen. Die erstellten Plots musst du erst als
Objekt speichern und kannst sie dann über +
zusammenfügen.
p1 <- ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point() +
labs(title = "Plot 1")
p2 <- ggplot(mpg, aes(x = drv, y = hwy)) +
geom_boxplot() +
labs(title = "Plot 2")
p1 + p2
Das Paket hat eine neue Funktionalität dem + Operator
zugefügt. | platziert p1 und p3
nebeneinander und / schiebt p2 in die nächste
Zeile.
p3 <- ggplot(mpg, aes(x = cty, y = hwy)) +
geom_point() +
labs(title = "Plot 3")
(p1 | p3) / p2
Patchwork erlaubt es uns die Legenden von mehreren Plots zu
bündeln. Die Platzierung der Legende kann angepasst werden, die
Dimensionen der Plots, ein gemeinsamer Titel, Untertitel, Überschrift,
etc.
Wir haben 5 Plots jetzt, die Legenden wurden unterdrückt und die Legende
oben erstellt mit & theme(legend.position = "top"). Die
Höhe der Plots wurde angepasst.
p1 <- ggplot(mpg, aes(x = drv, y = cty, color = drv)) +
geom_boxplot(show.legend = FALSE) +
labs(title = "Plot 1")
p2 <- ggplot(mpg, aes(x = drv, y = hwy, color = drv)) +
geom_boxplot(show.legend = FALSE) +
labs(title = "Plot 2")
p3 <- ggplot(mpg, aes(x = cty, color = drv, fill = drv)) +
geom_density(alpha = 0.5) +
labs(title = "Plot 3")
p4 <- ggplot(mpg, aes(x = hwy, color = drv, fill = drv)) +
geom_density(alpha = 0.5) +
labs(title = "Plot 4")
p5 <- ggplot(mpg, aes(x = cty, y = hwy, color = drv)) +
geom_point(show.legend = FALSE) +
facet_wrap(~drv) +
labs(title = "Plot 5")
(guide_area() / (p1 + p2) / (p3 + p4) / p5) +
plot_annotation(
title = "City and highway mileage for cars with different drive trains",
caption = "Source: Source: https://fueleconomy.gov."
) +
plot_layout(
guides = "collect",
heights = c(1, 3, 2, 4)
) &
theme(legend.position = "bottom")
Mehr dazu über die Hilfe oder link.
In diesem Abschnitt lernen wir Werkzeuge für logische Vektoren
kennen. Sie können nur TRUE, FALSE, oder
NA annehmen. In deiner Analyse findest du sie recht selten,
doch trotzdem werden sie fast immer kreiert und manipuliert.
Die meisten Funktionen, die wir brauchen, werden natürlich schon von Base R bereitgestellt. Um Data Frames zu bearbeiten und für Datenbeispiele, laden wir aber dennoch:
library(tidyverse)
library(nycflights13)
Ein gewöhnlicher Weg einen logischen Vektor zu erzeugen, ist es
numerische Vergleiche mithilfe von <,
<=, >, >=,
!= und == durchzuführen.
Bisher haben wir logische Variablen erzeugt mithilfe von
filter(). Sie wurden berechnet, benutzt und dann wieder
entsorgt. Im folgenden Beispiel finden sich über den Filter alle Abflüge
am Tag, die ungefähr pünktlich waren.
flights |>
filter(dep_time > 600 & dep_time < 2000 & abs(arr_delay) < 20)
## # A tibble: 172,286 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 601 600 1 844 850
## 2 2013 1 1 602 610 -8 812 820
## 3 2013 1 1 602 605 -3 821 805
## 4 2013 1 1 606 610 -4 858 910
## 5 2013 1 1 606 610 -4 837 845
## 6 2013 1 1 607 607 0 858 915
## 7 2013 1 1 611 600 11 945 931
## 8 2013 1 1 613 610 3 925 921
## 9 2013 1 1 615 615 0 833 842
## 10 2013 1 1 622 630 -8 1017 1014
## # ℹ 172,276 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Die logischen Variablen sind hier nicht sichtbar, wir können sie aber
sichtbar machen mit mutate():
flights |>
mutate(
daytime = dep_time > 600 & dep_time < 2000,
approx_ontime = abs(arr_delay) < 20,
.keep = "used"
)
## # A tibble: 336,776 × 4
## dep_time arr_delay daytime approx_ontime
## <int> <dbl> <lgl> <lgl>
## 1 517 11 FALSE TRUE
## 2 533 20 FALSE FALSE
## 3 542 33 FALSE FALSE
## 4 544 -18 FALSE TRUE
## 5 554 -25 FALSE FALSE
## 6 554 12 FALSE TRUE
## 7 555 19 FALSE TRUE
## 8 557 -14 FALSE TRUE
## 9 557 -8 FALSE TRUE
## 10 558 8 FALSE TRUE
## # ℹ 336,766 more rows
So können wir den Code besser verstehen und überprüfen, ob jeder Schritt korrekt ausgeführt wurde. Der Filter sieht dann so aus:
flights |>
mutate(
daytime = dep_time > 600 & dep_time < 2000,
approx_ontime = abs(arr_delay) < 20,
) |>
filter(daytime & approx_ontime)
## # A tibble: 172,286 × 21
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 601 600 1 844 850
## 2 2013 1 1 602 610 -8 812 820
## 3 2013 1 1 602 605 -3 821 805
## 4 2013 1 1 606 610 -4 858 910
## 5 2013 1 1 606 610 -4 837 845
## 6 2013 1 1 607 607 0 858 915
## 7 2013 1 1 611 600 11 945 931
## 8 2013 1 1 613 610 3 925 921
## 9 2013 1 1 615 615 0 833 842
## 10 2013 1 1 622 630 -8 1017 1014
## # ℹ 172,276 more rows
## # ℹ 13 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>, daytime <lgl>,
## # approx_ontime <lgl>
Punktvergleiche mit == können sehr gefährlich sein, da
sie ein falsches Ergebnis ausgeben können.
x <- c(1 / 49 * 49, sqrt(2) ^ 2)
x
## [1] 1 2
x == c(1, 2)
## [1] FALSE FALSE
Es wird von einer festen Anzahl an Nachkommastellen ausgegangen. Bei Wurzel aber auch mal gerundet.
print(x, digits = 16)
## [1] 0.9999999999999999 2.0000000000000004
Eine Option ist es dplyr::near() zu nutzen, dass
geringfügige Abweichungen ignoriert.
near(x, c(1, 2))
## [1] TRUE TRUE
Fast jede Operation, die einen unbekannten Wert involviert, ist wieder unbekannt.
NA > 5
10 == NA
NA == NA
# NA
Willst du z.B. alle unbekannte Flüge finden, bei denen
dept_time fehlt, so funktioniert
dept_time == NA nicht, da das Ergebnis immer
NA ist und filter() sortiert automatisch
fehlende Werte aus.
flights |>
filter(dep_time == NA)
is.na()is.na() funktioniert mit jedem Typ Vektor und gibt
TRUE aus für Missing Values und FALSE
für alles andere.
is.na(c(TRUE, NA, FALSE))
## [1] FALSE TRUE FALSE
is.na(c(1, NA, 3))
## [1] FALSE TRUE FALSE
is.na(c("a", NA, "b"))
## [1] FALSE TRUE FALSE
Jetzt können wir uns alle Zeilen ausgeben lassen, bei denen in
dept_time Missing Values vorkommen.
flights |>
filter(is.na(dep_time))
## # A tibble: 8,255 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 NA 1630 NA NA 1815
## 2 2013 1 1 NA 1935 NA NA 2240
## 3 2013 1 1 NA 1500 NA NA 1825
## 4 2013 1 1 NA 600 NA NA 901
## 5 2013 1 2 NA 1540 NA NA 1747
## 6 2013 1 2 NA 1620 NA NA 1746
## 7 2013 1 2 NA 1355 NA NA 1459
## 8 2013 1 2 NA 1420 NA NA 1644
## 9 2013 1 2 NA 1321 NA NA 1536
## 10 2013 1 2 NA 1545 NA NA 1910
## # ℹ 8,245 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
In arrange() werden alle Missing Values ans
Ende gesetzt. Das kannst du überschreiben, indem du nach
is.na() sotierst.
flights |>
filter(month == 1, day == 1) |>
arrange(dep_time)
## # A tibble: 842 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 832 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
flights |>
filter(month == 1, day == 1) |>
arrange(desc(is.na(dep_time)), dep_time)
## # A tibble: 842 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 NA 1630 NA NA 1815
## 2 2013 1 1 NA 1935 NA NA 2240
## 3 2013 1 1 NA 1500 NA NA 1825
## 4 2013 1 1 NA 600 NA NA 901
## 5 2013 1 1 517 515 2 830 819
## 6 2013 1 1 533 529 4 850 830
## 7 2013 1 1 542 540 2 923 850
## 8 2013 1 1 544 545 -1 1004 1022
## 9 2013 1 1 554 600 -6 812 837
## 10 2013 1 1 554 558 -4 740 728
## # ℹ 832 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Hast du einmal alle logical vectors, so kannst du sie
kombinieren: & ist “and”, | ist “oder”,
! ist “nicht” und xor() ist “exklusives oder”,
also nicht die Schnittmenge.
df <- tibble(x = c(TRUE, FALSE, NA))
df |>
mutate(
and = x & NA,
or = x | NA
)
## # A tibble: 3 × 3
## x and or
## <lgl> <lgl> <lgl>
## 1 TRUE NA TRUE
## 2 FALSE FALSE NA
## 3 NA NA NA
Ein NA in einem logischen Vektor bedeutet entweder
TRUE oder FALSE. TRUE | TRUE und
FALSE | TRUE sind beide TRUE, also muss
NA | TRUE auch TRUE sein. Ähnliches mit
NA & FALSE.
flights |>
filter(month == 11 | month == 12)
## # A tibble: 55,403 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 11 1 5 2359 6 352 345
## 2 2013 11 1 35 2250 105 123 2356
## 3 2013 11 1 455 500 -5 641 651
## 4 2013 11 1 539 545 -6 856 827
## 5 2013 11 1 542 545 -3 831 855
## 6 2013 11 1 549 600 -11 912 923
## 7 2013 11 1 550 600 -10 705 659
## 8 2013 11 1 554 600 -6 659 701
## 9 2013 11 1 554 600 -6 826 827
## 10 2013 11 1 554 600 -6 749 751
## # ℹ 55,393 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
funktioniert. Wenn du aber auf der rechten Seite eine Zahl benutzt,
so ist das wie ein TRUE.
flights |>
filter(month == 11 | 12)
## # A tibble: 336,776 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Vermeide dies, da jede Zeile dann ausgewählt wird.
flights |>
mutate(
nov = month == 11,
final = nov | 12,
.keep = "used"
)
## # A tibble: 336,776 × 3
## month nov final
## <int> <lgl> <lgl>
## 1 1 FALSE TRUE
## 2 1 FALSE TRUE
## 3 1 FALSE TRUE
## 4 1 FALSE TRUE
## 5 1 FALSE TRUE
## 6 1 FALSE TRUE
## 7 1 FALSE TRUE
## 8 1 FALSE TRUE
## 9 1 FALSE TRUE
## 10 1 FALSE TRUE
## # ℹ 336,766 more rows
%in%x %in% y gibt einen logischen Vektor der Länge von
x aus, der TRUE ist, wann immer ein Wert
x irgendwo in y ist.
1:12 %in% c(1, 5, 11)
## [1] TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE
letters[1:10] %in% c("a", "e", "i", "o", "u")
## [1] TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE FALSE
Um alle Flüge im November und Dezember zu finden, schreibe:
flights |>
filter(month %in% c(11, 12))
## # A tibble: 55,403 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 11 1 5 2359 6 352 345
## 2 2013 11 1 35 2250 105 123 2356
## 3 2013 11 1 455 500 -5 641 651
## 4 2013 11 1 539 545 -6 856 827
## 5 2013 11 1 542 545 -3 831 855
## 6 2013 11 1 549 600 -11 912 923
## 7 2013 11 1 550 600 -10 705 659
## 8 2013 11 1 554 600 -6 659 701
## 9 2013 11 1 554 600 -6 826 827
## 10 2013 11 1 554 600 -6 749 751
## # ℹ 55,393 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Hier gelten andere Regeln für NA.
c(1, 2, NA) == NA
#> [1] NA NA NA
c(1, 2, NA) %in% NA
#> [1] FALSE FALSE TRUE
So erhalten wir:
flights |>
filter(dep_time %in% c(NA, 0800))
## # A tibble: 8,803 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 800 800 0 1022 1014
## 2 2013 1 1 800 810 -10 949 955
## 3 2013 1 1 NA 1630 NA NA 1815
## 4 2013 1 1 NA 1935 NA NA 2240
## 5 2013 1 1 NA 1500 NA NA 1825
## 6 2013 1 1 NA 600 NA NA 901
## 7 2013 1 2 800 810 -10 1102 1116
## 8 2013 1 2 NA 1540 NA NA 1747
## 9 2013 1 2 NA 1620 NA NA 1746
## 10 2013 1 2 NA 1355 NA NA 1459
## # ℹ 8,793 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Nützliche Techniken für das Zusammenfassen von logischen Vektoren erscheinen jetzt.
Es gibt zwei logical summaries: any() und
all(). any(x) ist das Äquivalent zu
|; es gibt TRUE aus, wenn es irgendein
TRUE in x gibt. all(x) ist
äquivalent zu &. Es gibt TRUE zurück, wenn
alle Werte von x TRUE’s sind. NA
wird ausgegeben, wenn es Missing Values gibt, und zu vermeiden
ist dies mit na.rm = TRUE.
Gibt es Tage, an denen alle Flüge Verspätung hatten?
flights |>
group_by(year, month, day) |>
summarize(
all_delayed = all(arr_delay >= 0, na.rm = TRUE),
any_delayed = any(arr_delay >= 0, na.rm = TRUE),
.groups = "drop"
)
## # A tibble: 365 × 5
## year month day all_delayed any_delayed
## <int> <int> <int> <lgl> <lgl>
## 1 2013 1 1 FALSE TRUE
## 2 2013 1 2 FALSE TRUE
## 3 2013 1 3 FALSE TRUE
## 4 2013 1 4 FALSE TRUE
## 5 2013 1 5 FALSE TRUE
## 6 2013 1 6 FALSE TRUE
## 7 2013 1 7 FALSE TRUE
## 8 2013 1 8 FALSE TRUE
## 9 2013 1 9 FALSE TRUE
## 10 2013 1 10 FALSE TRUE
## # ℹ 355 more rows
Wenn du einen logischen Vektor in einem numerischen Kontext benutzt,
so wird TRUE zu 1 und FALSE zu
0. So lassen sich sum() und
mean() sehr nützlich mit logischen Vektoren vereinen, da
die Anzahl an TRUE’s und der Anteil ausgegeben wird.
flights |>
group_by(year, month, day) |>
summarize(
prop_delayed = mean(arr_delay > 0, na.rm = TRUE),
.groups = "drop"
) |>
ggplot(aes(x = prop_delayed)) +
geom_histogram(binwidth = 0.05)
Wieviele Flüge sind vor 5 Uhr abgeflogen?
flights |>
group_by(year, month, day) |>
summarize(
n_early = sum(dep_time < 500, na.rm = TRUE),
.groups = "drop"
) |>
arrange(desc(n_early))
## # A tibble: 365 × 4
## year month day n_early
## <int> <int> <int> <int>
## 1 2013 6 28 32
## 2 2013 4 10 30
## 3 2013 7 28 30
## 4 2013 3 18 29
## 5 2013 7 7 29
## 6 2013 7 10 29
## 7 2013 6 27 25
## 8 2013 6 13 24
## 9 2013 3 8 22
## 10 2013 7 22 22
## # ℹ 355 more rows
Wir können logical vector auch in summarize
benutzen, um hier eine einzelne Variable zu filtern. Die Nutzung der
eckigen Klammern hilft.
Wir wollen den durchschnittlichen Delay, also Verspätung,
berechnen. Also nur für Flüge mit Verspätung (>0). Eine
Möglichkeit:
flights |>
filter(arr_delay > 0) |>
group_by(year, month, day) |>
summarize(
behind = mean(arr_delay),
n = n(),
.groups = "drop"
)
## # A tibble: 365 × 5
## year month day behind n
## <int> <int> <int> <dbl> <int>
## 1 2013 1 1 32.5 461
## 2 2013 1 2 32.0 535
## 3 2013 1 3 27.7 460
## 4 2013 1 4 28.3 297
## 5 2013 1 5 22.6 238
## 6 2013 1 6 24.4 381
## 7 2013 1 7 27.8 243
## 8 2013 1 8 20.8 275
## 9 2013 1 9 25.6 287
## 10 2013 1 10 27.3 220
## # ℹ 355 more rows
Aber was, wenn wir auch die durchschnittliche Verspätung für Flüge berechnen wollen, die zu früh angekommen sind?
flights |>
group_by(year, month, day) |>
summarize(
behind = mean(arr_delay[arr_delay > 0], na.rm = TRUE),
ahead = mean(arr_delay[arr_delay < 0], na.rm = TRUE),
n = n(),
.groups = "drop"
)
## # A tibble: 365 × 6
## year month day behind ahead n
## <int> <int> <int> <dbl> <dbl> <int>
## 1 2013 1 1 32.5 -12.5 842
## 2 2013 1 2 32.0 -14.3 943
## 3 2013 1 3 27.7 -18.2 914
## 4 2013 1 4 28.3 -17.0 915
## 5 2013 1 5 22.6 -14.0 720
## 6 2013 1 6 24.4 -13.6 832
## 7 2013 1 7 27.8 -17.0 933
## 8 2013 1 8 20.8 -14.3 899
## 9 2013 1 9 25.6 -13.0 902
## 10 2013 1 10 27.3 -16.4 932
## # ℹ 355 more rows
Bedenke auch den Unterschied der Gruppengröße: delayed vs. total flights.
Hier gibt es zwei mächtige Werkzeuge: if_else() und
case_when().
if_else()Mit dplyr::if_else() kannst du einen Wert benutzen, wenn
die Bedingung TRUE ist und einen anderen, wenn sie
FALSE ist. Das erste der drei Argumente ist die
condition, ein logical vector. Dann
true, wenn die Bedingung erfüllt ist, und
false, der Output, falls die condition false
ist.
x <- c(-3:3, NA)
if_else(x > 0, "+ve", "-ve")
## [1] "-ve" "-ve" "-ve" "-ve" "+ve" "+ve" "+ve" NA
Es gibt tatsächlich ein viertes optionales Argument, falls der Input
NA ist.
if_else(x > 0, "+ve", "-ve", "???")
## [1] "-ve" "-ve" "-ve" "-ve" "+ve" "+ve" "+ve" "???"
Du kannst anstelle der true, false
Argumente auch Vektoren benutzen.
if_else(x < 0, -x, x)
## [1] 3 2 1 0 1 2 3 NA
x1 <- c(NA, 1, 2, NA)
y1 <- c(3, NA, 4, 6)
if_else(is.na(x1), y1, x1)
## [1] 3 1 2 6
if_else(x == 0, "0", if_else(x < 0, "-ve", "+ve"), "???")
## [1] "-ve" "-ve" "-ve" "0" "+ve" "+ve" "+ve" "???"
Die letzte Zeile ist hart zu lesen, deshalb wechseln wir lieber zu
dplyr::case_when().
case_when()Eine spezielle Syntax haben wir hier. Sie nimmt Paare vor:
condition ~ output. condition muss ein
logical vector sein. Bei Erfüllung wird output
genutzt.
case_when(
x == 0 ~ "0",
x < 0 ~ "-ve",
x > 0 ~ "+ve",
is.na(x) ~ "???"
)
## [1] "-ve" "-ve" "-ve" "0" "+ve" "+ve" "+ve" "???"
Wenn kein Fall passt, erhalten wir ein NA.
case_when(
x < 0 ~ "-ve",
x > 0 ~ "+ve"
)
## [1] "-ve" "-ve" "-ve" NA "+ve" "+ve" "+ve" NA
Für einen default, nutze TRUE auf der linken
Seite.
case_when(
x < 0 ~ "-ve",
x > 0 ~ "+ve",
TRUE ~ "???"
)
## [1] "-ve" "-ve" "-ve" "???" "+ve" "+ve" "+ve" "???"
Sind mehrere Bedingungen erfüllt, so wird nur der erste genutzt.
case_when(
x > 0 ~ "+ve",
x > 3 ~ "big"
)
## [1] NA NA NA NA "+ve" "+ve" "+ve" NA
Etwas so schönes können wir bauen:
flights |>
mutate(
status = case_when(
is.na(arr_delay) ~ "cancelled",
arr_delay < -30 ~ "very early",
arr_delay < -15 ~ "early",
abs(arr_delay) <= 15 ~ "on time",
arr_delay > 15 ~ "late",
arr_delay > 60 ~ "very late",
),
.keep = "used"
)
## # A tibble: 336,776 × 2
## arr_delay status
## <dbl> <chr>
## 1 11 on time
## 2 20 late
## 3 33 late
## 4 -18 early
## 5 -25 early
## 6 12 on time
## 7 19 late
## 8 -14 on time
## 9 -8 on time
## 10 8 on time
## # ℹ 336,766 more rows
ifelse() und case_when() erfordern passende
Typen. Passen sie nicht, gibt es Fehlermeldungen.
if_else(c(TRUE,FALSE), "a", 2)
Kompatibel sind:
NA-ä- ist kompatibel mit allem.Das Rückgrat der Data Science sind natürlich Zahlen. Was kannst du in R alles mit ihnen machen?
library(tidyverse)
library(nycflights13)
readr bietet zwei wichtige Funktionen für die
Transformation von Strings in Zahlen:
parse_double() und parse_number(). Benutze
parse_double(), wenn du Zahlen als Strings
geschrieben hast.
x <- c("1.2", "5.6", "1e3")
parse_double(x)
## [1] 1.2 5.6 1000.0
Benutze parse_number(), wenn dein String
nicht-numerischen Text enthält, den du ignorieren willst.
x <- c("$1,234", "USD 3,513", "59%")
parse_number(x)
## [1] 1234 3513 59
Für schnelle Entdeckungen und Checken ist diese Funktion
count() genial. Auch sortieren geht rasch.
flights |>
print(n = 10)
## # A tibble: 336,776 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
flights |> count(dest)
## # A tibble: 105 × 2
## dest n
## <chr> <int>
## 1 ABQ 254
## 2 ACK 265
## 3 ALB 439
## 4 ANC 8
## 5 ATL 17215
## 6 AUS 2439
## 7 AVL 275
## 8 BDL 443
## 9 BGR 375
## 10 BHM 297
## # ℹ 95 more rows
flights |> count(dest, sort = TRUE)
## # A tibble: 105 × 2
## dest n
## <chr> <int>
## 1 ORD 17283
## 2 ATL 17215
## 3 LAX 16174
## 4 BOS 15508
## 5 MCO 14082
## 6 CLT 14064
## 7 SFO 13331
## 8 FLL 12055
## 9 MIA 11728
## 10 DCA 9705
## # ℹ 95 more rows
Dieselbe Berechnung kannst du per Hand mit group_by()
und summarize() und n() vornehmen. Jetzt
kannst du auch andere Summaries vornehmen.
flights |>
group_by(dest) |>
summarize(
n = n(),
delay = mean(arr_delay, na.rm = TRUE)
)
## # A tibble: 105 × 3
## dest n delay
## <chr> <int> <dbl>
## 1 ABQ 254 4.38
## 2 ACK 265 4.85
## 3 ALB 439 14.4
## 4 ANC 8 -2.5
## 5 ATL 17215 11.3
## 6 AUS 2439 6.02
## 7 AVL 275 8.00
## 8 BDL 443 7.05
## 9 BGR 375 8.03
## 10 BHM 297 16.9
## # ℹ 95 more rows
n() funktioniert natürlich nur in der dplyr
Umgebung und brauch keine Argumente. Es gibt jedoch ein paar Varianten
von n():
n_distinct(x) zählt die Anzahl einzigartiger Werte
einer oder mehrerer Variablen. Welche Ziele werden von den meisten
Fluglinien angesteuert?flights |>
group_by(dest) |>
summarize(
carriers = n_distinct(carrier)
) |>
arrange(desc(carriers))
## # A tibble: 105 × 2
## dest carriers
## <chr> <int>
## 1 ATL 7
## 2 BOS 7
## 3 CLT 7
## 4 ORD 7
## 5 TPA 7
## 6 AUS 6
## 7 DCA 6
## 8 DTW 6
## 9 IAD 6
## 10 MSP 6
## # ℹ 95 more rows
flights |>
group_by(tailnum) |>
summarize(miles = sum(distance))
## # A tibble: 4,044 × 2
## tailnum miles
## <chr> <dbl>
## 1 D942DN 3418
## 2 N0EGMQ 250866
## 3 N10156 115966
## 4 N102UW 25722
## 5 N103US 24619
## 6 N104UW 25157
## 7 N10575 150194
## 8 N105UW 23618
## 9 N107US 21677
## 10 N108UW 32070
## # ℹ 4,034 more rows
count() mit dem Argument wt macht
dasselbe.
flights |> count(tailnum, wt = distance)
## # A tibble: 4,044 × 2
## tailnum n
## <chr> <dbl>
## 1 D942DN 3418
## 2 N0EGMQ 250866
## 3 N10156 115966
## 4 N102UW 25722
## 5 N103US 24619
## 6 N104UW 25157
## 7 N10575 150194
## 8 N105UW 23618
## 9 N107US 21677
## 10 N108UW 32070
## # ℹ 4,034 more rows
Missing Values kannst du zählen durch Kombinieren von
sum() und is.na(). Im flights
Datensatz sind es die gecancellten Flüge.
flights |>
group_by(dest) |>
summarize(n_cancelled = sum(is.na(dep_time)))
## # A tibble: 105 × 2
## dest n_cancelled
## <chr> <int>
## 1 ABQ 0
## 2 ACK 0
## 3 ALB 20
## 4 ANC 0
## 5 ATL 317
## 6 AUS 21
## 7 AVL 12
## 8 BDL 31
## 9 BGR 15
## 10 BHM 25
## # ℹ 95 more rows
Transformationsfunktionen funktionieren sehr gut mit
mutate(), weil ihr Output dieselbe Länge wie ihr Input
hat.
Die Basics wie Addition usw. sind bekannt. Was aber passiert, wenn
linke und rechte Seite verschiedene Längen haben?
flights |> mutate(air_time = air_time / 60). 336 766
Zahlen links und eine rechts. Der kürzere Vektor wird wiederholt.
x <- c(1, 2, 10, 20)
x / 5
## [1] 0.2 0.4 2.0 4.0
x / c(5, 5, 5, 5)
## [1] 0.2 0.4 2.0 4.0
Eine Warnung wird ausgegeben, wenn der längere kein Vielfaches des kürzeren Vektors ist.
x * c(1, 2, 3)
#> Warning in x * c(1, 2, 3): longer object length is not a multiple of shorter
Diese recycling rules werden auch auf logische Vergleiche
angewendet (==, <, <=,
>, >=, !=) und führen zu
überraschenden Ergebnissen, wenn man fälschlicherweise ==
statt %in% anwendet.
flights |>
filter(month == c(1, 2))
## # A tibble: 25,977 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 542 540 2 923 850
## 3 2013 1 1 554 600 -6 812 837
## 4 2013 1 1 555 600 -5 913 854
## 5 2013 1 1 557 600 -3 838 846
## 6 2013 1 1 558 600 -2 849 851
## 7 2013 1 1 558 600 -2 924 917
## 8 2013 1 1 559 600 -1 941 910
## 9 2013 1 1 559 600 -1 854 902
## 10 2013 1 1 600 600 0 837 825
## # ℹ 25,967 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Es findet natürlich nur Flüge in ungeraden Zeilen, die im Januar, und
Flüge in geraden Zeilen, die im Februar geflogen sind. Um uns vor
solchen Fehlern zu schützen: die meisten tidyverse
Funktionen benutzen eine striktere Form von Recycling, das nur einzelne
Werte recyclt.
Arithmetische Funktionen funktionieren gut mit Paaren von Variablen.
pmin() und pmax() geben den kleinsten oder
größten Wert in jeder Zeile zurück.
df <- tribble(
~x, ~y,
1, 3,
5, 2,
7, NA,
)
df |>
mutate(
min = pmin(x, y, na.rm = TRUE),
max = pmax(x, y, na.rm = TRUE)
)
## # A tibble: 3 × 4
## x y min max
## <dbl> <dbl> <dbl> <dbl>
## 1 1 3 1 3
## 2 5 2 2 5
## 3 7 NA 7 7
Sie sind ungleich min() und max().
df |>
mutate(
min = min(x, y, na.rm = TRUE),
max = max(x, y, na.rm = TRUE)
)
## # A tibble: 3 × 4
## x y min max
## <dbl> <dbl> <dbl> <dbl>
## 1 1 3 1 7
## 2 5 2 1 7
## 3 7 NA 1 7
1:10 %/% 3 # Ganze Zahl bei Division vor dem Komma
## [1] 0 0 1 1 1 2 2 2 3 3
1:10 %% 3 # Nachkommazahl bzw. Rest
## [1] 1 2 0 1 2 0 1 2 0 1
Moduare Arithmetic ist für die Flüge nützlich, da die Zeitvarable
sched_dep_time in hour und minute
umgerechnet werden kann:
flights |>
mutate(
hour = sched_dep_time %/% 100,
minute = sched_dep_time %% 100,
.keep = "used"
)
## # A tibble: 336,776 × 3
## sched_dep_time hour minute
## <int> <dbl> <dbl>
## 1 515 5 15
## 2 529 5 29
## 3 540 5 40
## 4 545 5 45
## 5 600 6 0
## 6 558 5 58
## 7 600 6 0
## 8 600 6 0
## 9 600 6 0
## 10 600 6 0
## # ℹ 336,766 more rows
So können wir den Anteil der gestrichenen Flüge über den Tag berechnen.
flights |>
group_by(hour = sched_dep_time %/% 100) |>
summarize(prop_cancelled = mean(is.na(dep_time)), n = n()) |>
filter(hour > 1) |>
ggplot(aes(x = hour, y = prop_cancelled)) +
geom_line(color = "grey50") +
geom_point(aes(size = n))
starting <- 100
interest <- 1.05
money <- tibble(
year = 1:50,
money = starting * interest ^ year
)
Eine Exponentialkurve zeigt dein Geldwachstum an.
ggplot(money, aes(x = year, y = money)) +
geom_line()
Logarithmische Transformation der y-Achse zeigt eine gerade Linie.
ggplot(money, aes(x = year, y = money)) +
geom_line() +
scale_y_log10()
Benutze round(x) zum Runden zum nächsten Nachbarn.
round(123.456)
## [1] 123
Präzisiere das Ganze über ein zweites Argument.
round(123.456, 2) # two digits
## [1] 123.46
round(123.456, 1) # one digit
## [1] 123.5
round(123.456, -1) # round to nearest ten
## [1] 120
round(123.456, -2) # round to nearest hundred
## [1] 100
Es wird bei .5 immer zur geraden Zahl gerundet. Abrunden und
Aufrunden durch floor() und ceiling().
x <- 123.456
floor(x)
## [1] 123
ceiling(x)
## [1] 124
x <- c(1, 2, 5, 10, 15, 20)
cut(x, breaks = c(0, 5, 10, 15, 20))
## [1] (0,5] (0,5] (0,5] (5,10] (10,15] (15,20]
## Levels: (0,5] (5,10] (10,15] (15,20]
cut(x, breaks = c(0, 5, 10, 100))
## [1] (0,5] (0,5] (0,5] (5,10] (10,100] (10,100]
## Levels: (0,5] (5,10] (10,100]
Benutze deine eigenen Labels, aber immer eins weniger als
breaks.
cut(x,
breaks = c(0, 5, 10, 15, 20),
labels = c("sm", "md", "lg", "xl")
)
## [1] sm sm sm md lg xl
## Levels: sm md lg xl
Jeder Wert, der nicht im Intervall ist, bekommt ein
NA.
y <- c(NA, -10, 5, 10, 30)
cut(y, breaks = c(0, 5, 10, 15, 20))
## [1] <NA> <NA> (0,5] (5,10] <NA>
## Levels: (0,5] (5,10] (10,15] (15,20]
x <- 1:10
cumsum(x)
## [1] 1 3 6 10 15 21 28 36 45 55
Im slider Paket gibt es komplexere Aggregate.
dplyr::min_rank() behandelt Bindungen als 1. 2. 2.
4.
x <- c(1, 4, 2, 3, 2, NA)
min_rank(x)
## [1] 1 5 2 4 2 NA
min_rank(desc(x))
## [1] 5 1 3 2 3 NA
Ähnliche Varianten findest du in der Dokumentation.
df <- tibble(x = x)
df |>
mutate(
row_number = row_number(x),
dense_rank = dense_rank(x),
percent_rank = percent_rank(x),
cume_dist = cume_dist(x)
)
## # A tibble: 6 × 5
## x row_number dense_rank percent_rank cume_dist
## <dbl> <int> <int> <dbl> <dbl>
## 1 1 1 1 0 0.2
## 2 4 5 4 1 1
## 3 2 2 2 0.25 0.6
## 4 3 4 3 0.75 0.8
## 5 2 3 2 0.25 0.6
## 6 NA NA NA NA NA
x <- c(2, 5, 11, 11, 19, 35)
lag(x) # Verschebung eins nach rechts
## [1] NA 2 5 11 11 19
lead(x) # Verschiebung eins nach links
## [1] 5 11 11 19 35 NA
x - lag(x) # Differenz zwischen aktuellem Wert und Vorgänger
## [1] NA 3 6 0 8 16
x == lag(x) # wechselt der aktuelle Wert?
## [1] NA FALSE FALSE TRUE FALSE FALSE
Über ein zweites Agument kannst du den “Lag” per Hand bestimmen.
consecutive_id()Zeiten, wann eine Website besucht wird:
events <- tibble(
time = c(0, 1, 2, 3, 5, 10, 12, 15, 17, 19, 20, 27, 28, 30)
)
Zeit zwischen zwei Besuchen, “gap” größer gleich 5 soll identifiziert werden.
events <- events |>
mutate(
diff = time - lag(time, default = first(time)),
gap = diff >= 5
)
events
## # A tibble: 14 × 3
## time diff gap
## <dbl> <dbl> <lgl>
## 1 0 0 FALSE
## 2 1 1 FALSE
## 3 2 1 FALSE
## 4 3 1 FALSE
## 5 5 2 FALSE
## 6 10 5 TRUE
## 7 12 2 FALSE
## 8 15 3 FALSE
## 9 17 2 FALSE
## 10 19 2 FALSE
## 11 20 1 FALSE
## 12 27 7 TRUE
## 13 28 1 FALSE
## 14 30 2 FALSE
Vom logischen Vektor wollen wir zu einer Gruppierungsvariable: mit
cur_group_id():
events <- events |>
group_by(gap) |>
mutate(group = cur_group_id())
events
## # A tibble: 14 × 4
## # Groups: gap [2]
## time diff gap group
## <dbl> <dbl> <lgl> <int>
## 1 0 0 FALSE 1
## 2 1 1 FALSE 1
## 3 2 1 FALSE 1
## 4 3 1 FALSE 1
## 5 5 2 FALSE 1
## 6 10 5 TRUE 2
## 7 12 2 FALSE 1
## 8 15 3 FALSE 1
## 9 17 2 FALSE 1
## 10 19 2 FALSE 1
## 11 20 1 FALSE 1
## 12 27 7 TRUE 2
## 13 28 1 FALSE 1
## 14 30 2 FALSE 1
mean() vs. median(). Je nach Ausreißer und
Form erhalten wir verschiedenen Ergebnisse. Bedenke die
Einkommensverteilung. Hier ist der mean mit Sicherheit
größer.
Bei unseren Flugverspätungen eben.
flights |>
group_by(year, month, day) |>
summarize(
mean = mean(dep_delay, na.rm = TRUE),
median = median(dep_delay, na.rm = TRUE),
n = n(),
.groups = "drop"
) |>
ggplot(aes(x = mean, y = median)) +
geom_abline(slope = 1, intercept = 0, color = "white", size = 2) +
geom_point()
flights |>
group_by(year, month, day) |>
summarize(
mean = mean(dep_delay, na.rm = TRUE),
median = median(dep_delay, na.rm = TRUE),
n = n(),
.groups = "drop"
)
## # A tibble: 365 × 6
## year month day mean median n
## <int> <int> <int> <dbl> <dbl> <int>
## 1 2013 1 1 11.5 -1 842
## 2 2013 1 2 13.9 0 943
## 3 2013 1 3 11.0 0 914
## 4 2013 1 4 8.95 -1 915
## 5 2013 1 5 5.73 -1 720
## 6 2013 1 6 7.15 -1 832
## 7 2013 1 7 5.42 -2 933
## 8 2013 1 8 2.55 -2 899
## 9 2013 1 9 2.28 -4 902
## 10 2013 1 10 2.84 -4 932
## # ℹ 355 more rows
quantile(x, 0.25), quantile(x, 0.5),
quantile(x, 0.95) sind selbsterklärend.
flights |>
group_by(year, month, day) |>
summarize(
max = max(dep_delay, na.rm = TRUE),
q95 = quantile(dep_delay, 0.95, na.rm = TRUE),
.groups = "drop"
)
## # A tibble: 365 × 5
## year month day max q95
## <int> <int> <int> <dbl> <dbl>
## 1 2013 1 1 853 70.1
## 2 2013 1 2 379 85
## 3 2013 1 3 291 68
## 4 2013 1 4 288 60
## 5 2013 1 5 327 41
## 6 2013 1 6 202 51
## 7 2013 1 7 366 51.6
## 8 2013 1 8 188 35.3
## 9 2013 1 9 1301 27.2
## 10 2013 1 10 1126 31
## # ℹ 355 more rows
Standardabweichung sd(x), Interquartilsabstand
IQR(). IGQ ist das 75% - 25% Quantil.
flights |>
group_by(origin, dest) |>
summarize(
distance_sd = IQR(distance),
n = n(),
.groups = "drop"
) |>
filter(distance_sd > 0)
## # A tibble: 2 × 4
## origin dest distance_sd n
## <chr> <chr> <dbl> <int>
## 1 EWR EGE 1 110
## 2 JFK EGE 1 103
Visualisiere die Verteilung bevor du zusammenfassende Statistiken berechnest.
flights |>
ggplot(aes(x = dep_delay)) +
geom_histogram(binwidth = 15)
## Warning: Removed 8255 rows containing non-finite values (`stat_bin()`).
flights |>
filter(dep_delay < 120) |>
ggplot(aes(x = dep_delay)) +
geom_histogram(binwidth = 5)
Checke, ob die Untergruppen die ganze Verteilung für alle wiederspiegeln.
flights |>
filter(dep_delay < 120) |>
ggplot(aes(x = dep_delay, group = interaction(day, month))) +
geom_freqpoly(binwidth = 5, alpha = 1/5)
Es gibt drei Funktionen, die man benutzen kann, um Werte zu
extrahieren, die an einer speziellen Position stehen:
first(x), last(x), nth(x).
flights |>
group_by(year, month, day) |>
summarize(
first_dep = first(dep_time),
fifth_dep = nth(dep_time, 5),
last_dep = last(dep_time)
)
Hier fehlt ein na.rm = T, daher benutze ich auf den
ganzen Datensatz ein na.omit().
flights |>
na.omit() |>
group_by(year, month, day) |>
summarize(
last_dep = last(dep_time)
)
## `summarise()` has grouped output by 'year', 'month'. You can override using the
## `.groups` argument.
## # A tibble: 365 × 4
## # Groups: year, month [12]
## year month day last_dep
## <int> <int> <int> <int>
## 1 2013 1 1 2356
## 2 2013 1 2 2354
## 3 2013 1 3 2349
## 4 2013 1 4 2358
## 5 2013 1 5 2357
## 6 2013 1 6 2355
## 7 2013 1 7 2359
## 8 2013 1 8 2351
## 9 2013 1 9 2252
## 10 2013 1 10 2320
## # ℹ 355 more rows
Werte aus Positionen ziehen, ist komplementär zu Filtern auf Rängen. Filtern gibt uns alle Variablen, mit jeder Beobachtung in einer eigenen Reihe.
flights |>
group_by(year, month, day) |>
mutate(r = min_rank(desc(sched_dep_time)), .keep = "used") |>
filter(r %in% c(1, max(r)))
## # A tibble: 1,195 × 5
## # Groups: year, month, day [365]
## year month day sched_dep_time r
## <int> <int> <int> <int> <int>
## 1 2013 1 1 515 842
## 2 2013 1 1 2359 1
## 3 2013 1 1 2359 1
## 4 2013 1 1 2359 1
## 5 2013 1 2 2359 1
## 6 2013 1 2 500 943
## 7 2013 1 2 2359 1
## 8 2013 1 2 2359 1
## 9 2013 1 3 2359 1
## 10 2013 1 3 2359 1
## # ℹ 1,185 more rows
mutate()Summary Functions werden normalerweise mit
summarize() gepaart. Aufgrund der* recycling rules
aber auch mit mutate(), besonders bei Standardisierung:
x / sum(x) - Anteil.(x - mean(x)) / sd(x) - Z-Score.x / first(x) - Index basierend auf erster
Beobachtung.Hier lernen wir etwas über String-Manipulationswerkzeuge.
library(babynames)
## Warning: Paket 'babynames' wurde unter R Version 4.2.3 erstellt
Das stringr Paket ist Teil des
tidyverse. Alle stringr Funktionen starten mit
str_.
string1 <- "This is a string"
string2 <- 'If I want to include a "quote" inside a string, I use single quotes'
Vergisst du einen Ausdruck zu schließen, so erscheint ein
+.
double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'"
backslash <- "\\"
x <- c(single_quote, double_quote, backslash)
x
## [1] "'" "\"" "\\"
str_view(x)
## [1] │ '
## [2] │ "
## [3] │ \
tricky <- "double_quote <- \"\\\"\" # or '\"'
single_quote <- '\\'' # or \"'\""
str_view(tricky)
## [1] │ double_quote <- "\"" # or '"'
## │ single_quote <- '\'' # or "'"
#> [1] │ double_quote <- "\"" # or '"'
#> │ single_quote <- '\'' # or "'"
Zu viele Backslashs. Statt dessen, nutze einen raw
string durch r"( und beende mit
)".
tricky <- r"(double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'")"
str_view(tricky)
## [1] │ double_quote <- "\"" # or '"'
## │ single_quote <- '\'' # or "'"
#> [1] │ double_quote <- "\"" # or '"'
#> │ single_quote <- '\'' # or "'"
\n - neue Zeile.
\t - Tab. \u \U - nicht-englische
Characters.
x <- c("one\ntwo", "one\ttwo", "\u00b5", "\U0001f604")
x
## [1] "one\ntwo" "one\ttwo" "µ" "😄"
#> [1] "one\ntwo" "one\ttwo" "µ" "😄"
str_view(x)
## [1] │ one
## │ two
## [2] │ one{\t}two
## [3] │ µ
## [4] │ 😄
#> [1] │ one
#> │ two
#> [2] │ one{\t}two
#> [3] │ µ
#> [4] │ 😄
str_c()Nimmt Vektoren an und gibt einen Character Vector aus.
str_c("x", "y")
## [1] "xy"
str_c("x", "y", "z")
## [1] "xyz"
str_c("Hello ", c("John", "Susan"))
## [1] "Hello John" "Hello Susan"
str_c(c("Hallo ", "Servus "), c("Andi ", "Andrea "), c("Ciao", "Servus"), c(" Adios", " Cheers"))
## [1] "Hallo Andi Ciao Adios" "Servus Andrea Servus Cheers"
str_c() ist designt, um mit mutate()
genutzt werden und gehorcht so den gewöhnlichen Regeln für
recycling und Missing Values.
set.seed(1410)
df <- tibble(name = c(wakefield::name(3), NA))
df |> mutate(greeting = str_c("Hi ", name, "!"))
## # A tibble: 4 × 2
## name greeting
## <chr> <chr>
## 1 Ilena Hi Ilena!
## 2 Sacramento Hi Sacramento!
## 3 Graylon Hi Graylon!
## 4 <NA> <NA>
Benutze coalesce(), um statt Missing Values
Alternativen ausgeben zu lassen.
df |>
mutate(
greeting1 = str_c("Hi ", coalesce(name, "you"), "!"),
greeting2 = coalesce(str_c("Hi ", name, "!"), "Hi!")
)
## # A tibble: 4 × 3
## name greeting1 greeting2
## <chr> <chr> <chr>
## 1 Ilena Hi Ilena! Hi Ilena!
## 2 Sacramento Hi Sacramento! Hi Sacramento!
## 3 Graylon Hi Graylon! Hi Graylon!
## 4 <NA> Hi you! Hi!
str_glue()Viele Gänsefüßchen mussten wir verwenden. Dies ist vermeidbar, wenn
wir str_glue() aus dem glue Paket
benutzen. Alles innerhalb {} wird bewertet, als wäre es
außerhalb der Anführungszeichen.
df |> mutate(greeting = str_glue("Hi {name}!"))
str_glue() konvertiert Missing Values zu
"NA". Jetzt ist es inkonsistent zu str_c().
Wenn du { oder } einfügen willst, nutze
{{ bzw. }}.
df |> mutate(greeting = str_glue("{{Hi {name}!}}"))
str_flatten()str_c() und glue() funktionieren gut mit
mutate(), weil ihr Output dieselbe Länge wie Input hat. Was
aber, wenn du nur einen einzelnen String ausgegeben haben
willst. str_flatten() nimmt einen Character Vektor
und kombiniert jedes Element des Vektors in einen single
String:
detach("package:tidyverse", unload = TRUE)
library(tidytable)
## Warning: Paket 'tidytable' wurde unter R Version 4.2.3 erstellt
## Warning: tidytable was loaded after dplyr.
## This can lead to most dplyr functions being overwritten by tidytable functions.
## Warning: tidytable was loaded after tidyr.
## This can lead to most tidyr functions being overwritten by tidytable functions.
##
## Attache Paket: 'tidytable'
## Die folgenden Objekte sind maskiert von 'package:dplyr':
##
## across, add_count, add_tally, anti_join, arrange, between,
## bind_cols, bind_rows, c_across, case_match, case_when, coalesce,
## consecutive_id, count, cross_join, cume_dist, cur_column, cur_data,
## cur_group_id, cur_group_rows, dense_rank, desc, distinct, filter,
## first, full_join, group_by, group_cols, group_split, group_vars,
## if_all, if_any, if_else, inner_join, is_grouped_df, lag, last,
## lead, left_join, min_rank, mutate, n, n_distinct, na_if, nest_by,
## nest_join, nth, percent_rank, pick, pull, recode, reframe,
## relocate, rename, rename_with, right_join, row_number, rowwise,
## select, semi_join, slice, slice_head, slice_max, slice_min,
## slice_sample, slice_tail, summarise, summarize, tally, top_n,
## transmute, tribble, ungroup
## Die folgenden Objekte sind maskiert von 'package:purrr':
##
## map, map_chr, map_dbl, map_df, map_dfc, map_dfr, map_int, map_lgl,
## map_vec, map2, map2_chr, map2_dbl, map2_df, map2_dfc, map2_dfr,
## map2_int, map2_lgl, map2_vec, pmap, pmap_chr, pmap_dbl, pmap_df,
## pmap_dfc, pmap_dfr, pmap_int, pmap_lgl, pmap_vec, walk
## Die folgenden Objekte sind maskiert von 'package:tidyr':
##
## complete, crossing, drop_na, expand, expand_grid, extract, fill,
## nest, nesting, pivot_longer, pivot_wider, replace_na, separate,
## separate_longer_delim, separate_rows, separate_wider_delim,
## separate_wider_regex, tribble, uncount, unite, unnest,
## unnest_longer, unnest_wider
## Die folgenden Objekte sind maskiert von 'package:tibble':
##
## enframe, tribble
## Die folgenden Objekte sind maskiert von 'package:stats':
##
## dt, filter, lag
## Das folgende Objekt ist maskiert 'package:base':
##
## %in%
str_flatten(c("x", "y", "z"))
## [1] "xyz"
str_flatten(c("x", "y", "z"), ", ")
## [1] "x, y, z"
# str_flatten(c("x", "y", "z"), ", ", last = ", and ")
Dadurch lässt es sich gut mit summarize() arbeiten:
df <- tribble(
~ name, ~ fruit,
"Carmen", "banana",
"Carmen", "apple",
"Marvin", "nectarine",
"Terence", "cantaloupe",
"Terence", "papaya",
"Terence", "madarine"
)
df |>
group_by(name) |>
summarize(fruits = str_flatten(fruit, ", "))
## # A tidytable: 3 × 2
## name fruits
## <chr> <chr>
## 1 Carmen banana, apple
## 2 Marvin nectarine
## 3 Terence cantaloupe, papaya, madarine
df |>
separate_longer_delim(col, delim)df |>
separate_longer_position(col, width)df |>
separate_wider_delim(col, delim, names)df |>
separate_wider_position(col, widths)df1 <- tibble(x = c("a,b,c", "d,e", "f"))
df1 |>
separate_longer_delim(x, delim = ",")
## # A tidytable: 6 × 1
## x
## <chr>
## 1 a
## 2 b
## 3 c
## 4 d
## 5 e
## 6 f
#> # A tibble: 6 × 1
#> x
#> <chr>
#> 1 a
#> 2 b
#> 3 c
#> 4 d
#> 5 e
#> 6 f
df2 <- tibble(x = c("1211", "131", "21"))
df2 |>
separate_longer_position(x, width = 1)
## # A tibble: 9 × 1
## x
## <chr>
## 1 1
## 2 2
## 3 1
## 4 1
## 5 1
## 6 3
## 7 1
## 8 2
## 9 1
#> # A tibble: 9 × 1
#> x
#> <chr>
#> 1 1
#> 2 2
#> 3 1
#> 4 1
#> 5 1
#> 6 3
#> # … with 3 more rows
df3 <- tibble(x = c("a10.1.2022", "b10.2.2011", "e15.1.2015"))
df3 |>
separate_wider_delim(
x,
delim = ".",
names = c("code", "edition", "year")
)
## # A tidytable: 3 × 3
## code edition year
## <chr> <chr> <chr>
## 1 a10 1 2022
## 2 b10 2 2011
## 3 e15 1 2015
#> # A tibble: 3 × 3
#> code edition year
#> <chr> <chr> <chr>
#> 1 a10 1 2022
#> 2 b10 2 2011
#> 3 e15 1 2015
df3 |>
separate_wider_delim(
x,
delim = ".",
names = c("code", NA, "year")
)
## # A tidytable: 3 × 2
## code year
## <chr> <chr>
## 1 a10 2022
## 2 b10 2011
## 3 e15 2015
#> # A tibble: 3 × 2
#> code year
#> <chr> <chr>
#> 1 a10 2022
#> 2 b10 2011
#> 3 e15 2015
df4 <- tibble(x = c("202215TX", "202122LA", "202325CA"))
df4 |>
separate_wider_position(
x,
widths = c(year = 4, age = 2, state = 2)
)
## # A tibble: 3 × 3
## year age state
## <chr> <chr> <chr>
## 1 2022 15 TX
## 2 2021 22 LA
## 3 2023 25 CA
#> # A tibble: 3 × 3
#> year age state
#> <chr> <chr> <chr>
#> 1 2022 15 TX
#> 2 2021 22 LA
#> 3 2023 25 CA
separate_wider_delim()df <- tibble(x = c("1-1-1", "1-1-2", "1-3", "1-3-2", "1"))
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z")
)
## # A tidytable: 5 × 3
## x y z
## <chr> <chr> <chr>
## 1 1 1 1
## 2 1 1 2
## 3 1 3 <NA>
## 4 1 3 2
## 5 1 <NA> <NA>
df <- tibble(x = c("1-1-1", "1-1-2", "1-3-5-6", "1-3-2", "1-3-5-7-9"))
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z")
)
## # A tidytable: 5 × 3
## x y z
## <chr> <chr> <chr>
## 1 1 1 1
## 2 1 1 2
## 3 1 3 5
## 4 1 3 2
## 5 1 3 5
detach("package:tidytable", unload = TRUE)
library(tidyverse)
## Warning: Paket 'tidyverse' wurde unter R Version 4.2.3 erstellt
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ scales::col_factor() masks readr::col_factor()
## ✖ scales::discard() masks purrr::discard()
## ✖ arrow::duration() masks lubridate::duration()
## ✖ dplyr::filter() masks stats::filter()
## ✖ recipes::fixed() masks stringr::fixed()
## ✖ dplyr::lag() masks stats::lag()
## ✖ yardstick::spec() masks readr::spec()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
df
## # A tibble: 5 × 1
## x
## <chr>
## 1 1-1-1
## 2 1-1-2
## 3 1-3-5-6
## 4 1-3-2
## 5 1-3-5-7-9
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_many = "drop"
)
## # A tibble: 5 × 3
## x y z
## <chr> <chr> <chr>
## 1 1 1 1
## 2 1 1 2
## 3 1 3 5
## 4 1 3 2
## 5 1 3 5
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_many = "merge"
)
## # A tibble: 5 × 3
## x y z
## <chr> <chr> <chr>
## 1 1 1 1
## 2 1 1 2
## 3 1 3 5-6
## 4 1 3 2
## 5 1 3 5-7-9
In diesem Abschnitt stellen wir Funktionen vor, die es uns erlauben die Buchstaben innerhalb eines Strings zu bearbeiten.
str_length() gibt die Anzahl der Buchstaben eines
Strings aus.
str_length(c("a", "R for data science", NA))
## [1] 1 18 NA
Du kannst die count() Funktion nutzen, um die Anzahl der
Buchstaben von Babynamen zu bestimmen.
library(babynames)
babynames |>
count(length = str_length(name), wt = n)
## # A tibble: 14 × 2
## length n
## <int> <int>
## 1 2 338150
## 2 3 8589596
## 3 4 48506739
## 4 5 87011607
## 5 6 90749404
## 6 7 72120767
## 7 8 25404066
## 8 9 11926551
## 9 10 1306159
## 10 11 2135827
## 11 12 16295
## 12 13 10845
## 13 14 3681
## 14 15 830
babynames |>
filter(str_length(name) == 15) |>
count(name, wt = n, sort = TRUE)
## # A tibble: 34 × 2
## name n
## <chr> <int>
## 1 Franciscojavier 123
## 2 Christopherjohn 118
## 3 Johnchristopher 118
## 4 Christopherjame 108
## 5 Christophermich 52
## 6 Ryanchristopher 45
## 7 Mariadelosangel 28
## 8 Jonathanmichael 25
## 9 Christianjoseph 22
## 10 Christopherjose 22
## # ℹ 24 more rows
Sage bitte, wie die ersten drei Buchstaben eines Substrings
lauten (str_sub(string, start, end)).
x <- c("Apple", "Banana", "Pear")
str_sub(x, 1, 3)
## [1] "App" "Ban" "Pea"
Negative Zahlen kannst du benutzen, um vom Ende zurück zu zählen.
str_sub(x, -3, -1)
## [1] "ple" "ana" "ear"
Wir können str_sub() mit mutate() benutzen,
um den ersten und letzten Buchstaben eines jeden Namens zu finden.
babynames |>
mutate(
first = str_sub(name, 1, 1),
last = str_sub(name, -1, -1)
)
## # A tibble: 1,924,665 × 7
## year sex name n prop first last
## <dbl> <chr> <chr> <int> <dbl> <chr> <chr>
## 1 1880 F Mary 7065 0.0724 M y
## 2 1880 F Anna 2604 0.0267 A a
## 3 1880 F Emma 2003 0.0205 E a
## 4 1880 F Elizabeth 1939 0.0199 E h
## 5 1880 F Minnie 1746 0.0179 M e
## 6 1880 F Margaret 1578 0.0162 M t
## 7 1880 F Ida 1472 0.0151 I a
## 8 1880 F Alice 1414 0.0145 A e
## 9 1880 F Bertha 1320 0.0135 B a
## 10 1880 F Sarah 1288 0.0132 S h
## # ℹ 1,924,655 more rows
str_trunc(x, 30): kein String hat mehr Zeichen,
als 30. Alles dahinter wird durch ... ersetzt.
str_wrap(x, 30) gibt einem String, der “zu lang”
ist, eine neue Zeile.
x <- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
str_view(str_trunc(x, 30), "[aeiou]")
## [1] │ L<o>r<e>m <i>ps<u>m d<o>l<o>r s<i>t <a>m<e>t,...
str_view(str_wrap(x, 30), "[aeiou]")
## [1] │ L<o>r<e>m <i>ps<u>m d<o>l<o>r s<i>t <a>m<e>t,
## │ c<o>ns<e>ct<e>t<u>r <a>d<i>p<i>sc<i>ng
## │ <e>l<i>t, s<e>d d<o> <e><i><u>sm<o>d t<e>mp<o>r
## │ <i>nc<i>d<i>d<u>nt <u>t l<a>b<o>r<e> <e>t d<o>l<o>r<e>
## │ m<a>gn<a> <a>l<i>q<u><a>. Ut <e>n<i>m <a>d
## │ m<i>n<i>m v<e>n<i><a>m, q<u><i>s n<o>str<u>d
## │ <e>x<e>rc<i>t<a>t<i><o>n <u>ll<a>mc<o> l<a>b<o>r<i>s
## │ n<i>s<i> <u>t <a>l<i>q<u><i>p <e>x <e><a> c<o>mm<o>d<o>
## │ c<o>ns<e>q<u><a>t.
In diesem Kapitel konzentrieren wir uns auf Funktionen, die Regular Expressions benutzen. Eine mächtige Sprache, um Muster innerhalb von Strings zu beschreiben.
library(tidyverse)
library(babynames)
Das zweite Argument wird optisch in der Ausgabe hervorgehoben. Hier also z.B. blackberry durch blau und <…>.
str_view(fruit, "berry")
## [6] │ bil<berry>
## [7] │ black<berry>
## [10] │ blue<berry>
## [11] │ boysen<berry>
## [19] │ cloud<berry>
## [21] │ cran<berry>
## [29] │ elder<berry>
## [32] │ goji <berry>
## [33] │ goose<berry>
## [38] │ huckle<berry>
## [50] │ mul<berry>
## [70] │ rasp<berry>
## [73] │ salal <berry>
## [76] │ straw<berry>
#> [6] │ bil<berry>
#> [7] │ black<berry>
#> [10] │ blue<berry>
#> [11] │ boysen<berry>
#> [19] │ cloud<berry>
#> [21] │ cran<berry>
#> [29] │ elder<berry>
#> [32] │ goji <berry>
#> [33] │ goose<berry>
#> [38] │ huckle<berry>
#> ... and 4 more
a. matcht jeden String, der ein “a” enthält,
gefolgt von weiterem Character.
str_view(c("a", "ab", "ae", "bd", "ea", "eab"), "a.")
## [2] │ <ab>
## [3] │ <ae>
## [6] │ e<ab>
#> [2] │ <ab>
#> [3] │ <ae>
#> [6] │ e<ab>
Wir können alle Früchte finden, die ein “a” enthalten, gefolgt von drei Buchstaben, gefolgt von einem “e”.
str_view(fruit, "a...e")
## [1] │ <apple>
## [7] │ bl<ackbe>rry
## [48] │ mand<arine>
## [51] │ nect<arine>
## [62] │ pine<apple>
## [64] │ pomegr<anate>
## [70] │ r<aspbe>rry
## [73] │ sal<al be>rry
#> [1] │ <apple>
#> [7] │ bl<ackbe>rry
#> [48] │ mand<arine>
#> [51] │ nect<arine>
#> [62] │ pine<apple>
#> [64] │ pomegr<anate>
#> [70] │ r<aspbe>rry
#> [73] │ sal<al be>rry
Quantifiers kontrollieren wie oft ein Muster zutrifft.
?: Muster optional - trifft 0 oder 1 mal zu+: Muster wiederholt sich - es trifft mind. einmal
zu*: Muster optional oder wiederholt sich# ab? matches an "a", optionally followed by a "b".
str_view(c("a", "ab", "abb"), "ab?")
## [1] │ <a>
## [2] │ <ab>
## [3] │ <ab>b
#> [1] │ <a>
#> [2] │ <ab>
#> [3] │ <ab>b
# ab+ matches an "a", followed by at least one "b".
str_view(c("a", "ab", "abb"), "ab+")
## [2] │ <ab>
## [3] │ <abb>
#> [2] │ <ab>
#> [3] │ <abb>
# ab* matches an "a", followed by any number of "b"s.
str_view(c("a", "ab", "abb"), "ab*")
## [1] │ <a>
## [2] │ <ab>
## [3] │ <abb>
#> [1] │ <a>
#> [2] │ <ab>
#> [3] │ <abb>
Character classes sind durch []
definiert. Buchstaben darin enthalten matchen: [abcd] -
“a”, “b”, “c”, “d”. Alles außer “a”, “b”, “c”, “d” matcht:
[^abcd]. Finde Wörter mit 3 Vokalen bzw. 4 Konsonanten
hintereinander.
str_view(words, "[aeiou][aeiou][aeiou]")
## [79] │ b<eau>ty
## [565] │ obv<iou>s
## [644] │ prev<iou>s
## [670] │ q<uie>t
## [741] │ ser<iou>s
## [915] │ var<iou>s
#> [79] │ b<eau>ty
#> [565] │ obv<iou>s
#> [644] │ prev<iou>s
#> [670] │ q<uie>t
#> [741] │ ser<iou>s
#> [915] │ var<iou>s
str_view(words, "[^aeiou][^aeiou][^aeiou][^aeiou]")
## [45] │ a<pply>
## [198] │ cou<ntry>
## [424] │ indu<stry>
## [830] │ su<pply>
## [836] │ <syst>em
#> [45] │ a<pply>
#> [198] │ cou<ntry>
#> [424] │ indu<stry>
#> [830] │ su<pply>
#> [836] │ <syst>em
Zwei Vokale gefolgt von mind. zwei Konsonanten:
str_view(words, "[aeiou][aeiou][^aeiou][^aeiou]+")
## [6] │ acc<ount>
## [21] │ ag<ainst>
## [31] │ alr<eady>
## [34] │ alth<ough>
## [37] │ am<ount>
## [46] │ app<oint>
## [47] │ appr<oach>
## [52] │ ar<ound>
## [61] │ <auth>ority
## [79] │ be<auty>
## [100] │ b<oard>
## [112] │ brill<iant>
## [117] │ b<uild>
## [139] │ ch<airm>an
## [158] │ cl<ient>
## [195] │ c<ould>
## [196] │ c<ounc>il
## [197] │ c<ount>
## [198] │ c<ountry>
## [199] │ c<ounty>
## ... and 52 more
#> [6] │ acc<ount>
#> [21] │ ag<ainst>
#> [31] │ alr<eady>
#> [34] │ alth<ough>
#> [37] │ am<ount>
#> [46] │ app<oint>
#> [47] │ appr<oach>
#> [52] │ ar<ound>
#> [61] │ <auth>ority
#> [79] │ be<auty>
#> ... and 62 more
Alternation, | sucht zwischen Mustern.
“apple”, “pear”, “banana”. Oder ein wiederholter Vokal.
str_view(fruit, "apple|pear|banana")
#> [1] │ <apple>
#> [4] │ <banana>
#> [59] │ <pear>
#> [62] │ pine<apple>
str_view(fruit, "aa|ee|ii|oo|uu")
#> [9] │ bl<oo>d orange
#> [33] │ g<oo>seberry
#> [47] │ lych<ee>
#> [66] │ purple mangost<ee>n
Da die Basics sitzen, benutzen wir sie zusammen mit
stringr und tidyr Funktionen: detect,
count, replace und extract.
str_detect() gibt einen logical vector, der
TRUE ist aus, wenn das Muster ein Elemment des
Character Vectors trifft, ansonsten FALSE.
str_detect(c("a", "b", "c"), "[aeiou]")
## [1] TRUE FALSE FALSE
Da str_detect() einen logical vector ausgibt,
passt es gut zu filter(). Der Code findet alle Namen, die
ein kleines “x” enthalten.
babynames |>
filter(str_detect(name, "x")) |>
count(name, wt = n, sort = TRUE)
## # A tibble: 974 × 2
## name n
## <chr> <int>
## 1 Alexander 665492
## 2 Alexis 399551
## 3 Alex 278705
## 4 Alexandra 232223
## 5 Max 148787
## 6 Alexa 123032
## 7 Maxine 112261
## 8 Alexandria 97679
## 9 Maxwell 90486
## 10 Jaxon 71234
## # ℹ 964 more rows
str_detect() können wir auch mit
summarize() nutzen:
sum(str_detect(x, pattern)) gibt die Anzahl der
Beobachtungen aus, die matchen und mit mean(...) die
Anteile.
babynames |>
group_by(year) |>
summarize(prop_x = mean(str_detect(name, "x"))) |>
ggplot(aes(x = year, y = prop_x)) +
geom_line()
Zwei Funktionen sind eng verwandt mit str_detect():
str_subst(), das die treffenden Strings ausgibt
und str_which(), das die treffenden Indices ausgibt.
str_subset(c("a", "b", "c"), "[aeiou]")
## [1] "a"
str_which(c("a", "b", "c"), "[aeiou]")
## [1] 1
str_count() sagt uns, wieviele Matches in jedem
String sind.
x <- c("apple", "banana", "pear")
str_count(x, "p")
## [1] 2 0 1
Wieviele Vokale und Konsonante sind in jedem Namen?
babynames |>
count(name) |>
mutate(
vowels = str_count(name, "[aeiou]"),
consonants = str_count(name, "[^aeiou]")
)
## # A tibble: 97,310 × 4
## name n vowels consonants
## <chr> <int> <int> <int>
## 1 Aaban 10 2 3
## 2 Aabha 5 2 3
## 3 Aabid 2 2 3
## 4 Aabir 1 2 3
## 5 Aabriella 5 4 5
## 6 Aada 1 2 2
## 7 Aadam 26 2 3
## 8 Aadan 11 2 3
## 9 Aadarsh 17 2 5
## 10 Aaden 18 2 3
## # ℹ 97,300 more rows
Regular Expressions sind case sensitive! Drei Möglichkeiten es zu beheben:
str_count(name, "[aeiouAEIOU]").str_count(regex(name, ignore_case = TRUE), "[aeiou]")
ignoriere Case.str_to_lower(), um die Namen in lower case zu
konvertieren:
str_count(str_to_lower(name), "[aeiou]").babynames |>
count(name) |>
mutate(
name = str_to_lower(name),
vowels = str_count(name, "[aeiou]"),
consonants = str_count(name, "[^aeiou]")
)
## # A tibble: 97,310 × 4
## name n vowels consonants
## <chr> <int> <int> <int>
## 1 aaban 10 3 2
## 2 aabha 5 3 2
## 3 aabid 2 3 2
## 4 aabir 1 3 2
## 5 aabriella 5 5 4
## 6 aada 1 3 1
## 7 aadam 26 3 2
## 8 aadan 11 3 2
## 9 aadarsh 17 3 4
## 10 aaden 18 3 2
## # ℹ 97,300 more rows
Wir können Matches modifizieren mit str_replace() und
str_replace_all().
x <- c("apple", "pear", "banana")
str_replace_all(x, "[aeiou]", "-")
## [1] "-ppl-" "p--r" "b-n-n-"
str_remove() und str_remove_all() sind
Abkürzungen für str_replace(x, pattern, "").
x <- c("apple", "pear", "banana")
str_remove_all(x, "[aeiou]")
## [1] "ppl" "pr" "bnn"
# To create the regular expression \., we need to use \\.
dot <- "\\."
# But the expression itself only contains one \
str_view(dot)
## [1] │ \.
#> [1] │ \.
# And this tells R to look for an explicit .
str_view(c("abc", "a.c", "bef"), "a\\.c")
## [2] │ <a.c>
#> [2] │ <a.c>
x <- "a\\b"
str_view(x)
## [1] │ a\b
#> [1] │ a\b
str_view(x, "\\\\")
## [1] │ a<\>b
#> [1] │ a<\>b
str_view(x, r"{\\}")
## [1] │ a<\>b
#> [1] │ a<\>b
Wenn du versuchst ein Buchtabnesymbol (Literal) zu matchen
(., $, |, *,
+, ?, {, },
(, )), dann gibt es eine Alternative zur
Nutzung des Escape: [.], [$], …
str_view(c("abc", "a.c", "a*c", "a c"), "a[.]c")
## [2] │ <a.c>
#> [2] │ <a.c>
str_view(c("abc", "a.c", "a*c", "a c"), ".[*]c")
## [3] │ <a*c>
#> [3] │ <a*c>
Wenn du am Anfang matchen willst: ^, am Ende:
$.
str_view(fruit, "^a")
## [1] │ <a>pple
## [2] │ <a>pricot
## [3] │ <a>vocado
#> [1] │ <a>pple
#> [2] │ <a>pricot
#> [3] │ <a>vocado
str_view(fruit, "a$")
## [4] │ banan<a>
## [15] │ cherimoy<a>
## [30] │ feijo<a>
## [36] │ guav<a>
## [56] │ papay<a>
## [74] │ satsum<a>
#> [4] │ banan<a>
#> [15] │ cherimoy<a>
#> [30] │ feijo<a>
#> [36] │ guav<a>
#> [56] │ papay<a>
#> [74] │ satsum<a>
Full String mit ^ und $:
str_view(fruit, "apple")
## [1] │ <apple>
## [62] │ pine<apple>
#> [1] │ <apple>
#> [62] │ pine<apple>
str_view(fruit, "^apple$")
## [1] │ <apple>
#> [1] │ <apple>
Matche die Grenze zwischen zwei Wörtern (Start, Ende) mit
\b. Finde so alle Nutzungen von sum(). Suche
nach \bsum\b, um summarize,
rowsum oder ähnliches zu vermeiden.
x <- c("summary(x)", "summarize(df)", "rowsum(x)", "sum(x)")
str_view(x, "sum")
## [1] │ <sum>mary(x)
## [2] │ <sum>marize(df)
## [3] │ row<sum>(x)
## [4] │ <sum>(x)
#> [1] │ <sum>mary(x)
#> [2] │ <sum>marize(df)
#> [3] │ row<sum>(x)
#> [4] │ <sum>(x)
str_view(x, "\\bsum\\b")
## [4] │ <sum>(x)
#> [4] │ <sum>(x)
str_view("abc", c("$", "^", "\\b"))
## [1] │ abc<>
## [2] │ <>abc
## [3] │ <>abc<>
#> [1] │ abc<>
#> [2] │ <>abc
#> [3] │ <>abc<>
str_replace_all("abc", c("$", "^", "\\b"), "--")
## [1] "abc--" "--abc" "--abc--"
Du kannst deine eigenen Mengen mit [] konstruieren, wo
[abc] a, b, oder c matcht. Es gibt drei
Character, die innerhalb der eckigen Klammern eine
spezielle Bedeutung haben:
[a-z], die Kleinbuchstaben
matcht und [0-9] Zahlen.^ Inverse nimmt ales bis auf a, b oder c auf:
[^abc].\ trennt Zeichen, so dass ^ oder
- oder ] matcht: [\^\-\]].x <- "abcd ABCD 12345 -!@#%."
str_view(x, "[abc]+")
## [1] │ <abc>d ABCD 12345 -!@#%.
#> [1] │ <abc>d ABCD 12345 -!@#%.
str_view(x, "[a-z]+")
## [1] │ <abcd> ABCD 12345 -!@#%.
#> [1] │ <abcd> ABCD 12345 -!@#%.
str_view(x, "[^a-z0-9]+")
## [1] │ abcd< ABCD >12345< -!@#%.>
#> [1] │ abcd< ABCD >12345< -!@#%.>
# You need an escape to match characters that are otherwise
# special inside of []
str_view("a-b-c", "[a-c]")
## [1] │ <a>-<b>-<c>
#> [1] │ <a>-<b>-<c>
str_view("a-b-c", "[a\\-c]")
## [1] │ <a><->b<-><c>
#> [1] │ <a><->b<-><c>
Faktoren werden für kategoriale Variablen genutzt; Variablen, die eine feste Menge an möglichen Werten haben.
Das forcats Paket ist Teil von
tidyverse.
library(tidyverse)
x1 <- c("Dec", "Apr", "Jan", "Mar")
Als String diese Werte aufzunehmen, hat zwei Probleme:
x2 <- c("Dec", "Apr", "Jam", "Mar")
sort(x1)
## [1] "Apr" "Dec" "Jan" "Mar"
Um einen Faktor zu kreieren, starte mit einer Liste valider levels:
month_levels <- c(
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
)
Daraus kannst du einen Faktor bauen:
y1 <- factor(x1, levels = month_levels)
y1
## [1] Dec Apr Jan Mar
## Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
sort(y1)
## [1] Jan Mar Apr Dec
## Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Jeder Wert, der sich nicht in levels befindet, wird zu
NA konvertiert.
y2 <- factor(x2, levels = month_levels)
y2
## [1] Dec Apr <NA> Mar
## Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
In alphabetischer Reihenfolge werden die Daten genommen, wenn du die Levels vergisst.
factor(x1)
## [1] Dec Apr Jan Mar
## Levels: Apr Dec Jan Mar
Du kannst die Reihenfolge nach dem erstmaligen Erscheinen festlegen:
f1 <- factor(x1, levels = unique(x1))
f1
## [1] Dec Apr Jan Mar
## Levels: Dec Apr Jan Mar
f2 <- x1 |> factor() |> fct_inorder()
f2
## [1] Dec Apr Jan Mar
## Levels: Dec Apr Jan Mar
Beim Einlesen der Daten kannst du direkt einen Faktor erzeugen mit
col_factor():
library(readr)
month_levels <- c(
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
)
csv <- "
month,value
Jan,12
Feb,56
Mar,12"
df <- read_csv(csv, col_types = cols(month = col_factor(month_levels)))
df$month
#> [1] Jan Feb Mar
#> Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Manchmal macht es Sinn die Reihenfolge der Faktoren zu ändern. Schauen wir uns die durchschnittliche TV Zeit pro Tag und Religionen an.
relig_summary <- gss_cat |>
group_by(relig) |>
summarize(
age = mean(age, na.rm = TRUE),
tvhours = mean(tvhours, na.rm = TRUE),
n = n()
)
ggplot(relig_summary, aes(x = tvhours, y = relig)) +
geom_point()
Der Plot ist schwer zu lesen, da es hier kein Muster gibt.
fct_reorder() sortiert um, und nimmt dabei drei Argumente
an:
f, der Faktor, wessen Levels du modifizieren
willst.x, ein numerischer Vektor, den du benutzt, um die
Reihenfolge zu bestimmen.fun, eine Funktion, wenn es multiple Werte
von x gibt, für jeden Wert von f. Default ist
Median.ggplot(relig_summary, aes(x = tvhours, y = fct_reorder(relig, tvhours))) +
geom_point()
Bei komplizierten Transformationen empfehlen wir sie aus
aes() zu nehmen und in ein separates mutate()
zu stecken.
relig_summary |>
mutate(
relig = fct_reorder(relig, tvhours)
) |>
ggplot(aes(x = tvhours, y = relig)) +
geom_point()
Wie sieht es mit der Altersverteilung in bestimmten Einkommensstufen aus?
rincome_summary <- gss_cat |>
group_by(rincome) |>
summarize(
age = mean(age, na.rm = TRUE),
tvhours = mean(tvhours, na.rm = TRUE),
n = n()
)
rincome_summary
## # A tibble: 16 × 4
## rincome age tvhours n
## <fct> <dbl> <dbl> <int>
## 1 No answer 45.5 2.90 183
## 2 Don't know 45.6 3.41 267
## 3 Refused 47.6 2.48 975
## 4 $25000 or more 44.2 2.23 7363
## 5 $20000 - 24999 41.5 2.78 1283
## 6 $15000 - 19999 40.0 2.91 1048
## 7 $10000 - 14999 41.1 3.02 1168
## 8 $8000 to 9999 41.1 3.15 340
## 9 $7000 to 7999 38.2 2.65 188
## 10 $6000 to 6999 40.3 3.17 215
## 11 $5000 to 5999 37.8 3.16 227
## 12 $4000 to 4999 38.9 3.15 226
## 13 $3000 to 3999 37.8 3.31 276
## 14 $1000 to 2999 34.5 3.00 395
## 15 Lt $1000 40.5 3.36 286
## 16 Not applicable 56.1 3.79 7043
Und dann bitte nett darstellen.
ggplot(rincome_summary, aes(x = age, y = fct_reorder(rincome, age))) +
geom_point()
Die Reihenfolge hier sollte jedoch noch einmal überdacht werden. Am
Anfang sollte jedoch “Not applicable” stehen. Nutze
fct_relevel(). Es nimmt einen Faktor f, dann
jede Anzahl an Levels, die du in die erste Zeile(n) schreiben
willst.
ggplot(rincome_summary, aes(x = age, y = fct_relevel(rincome, "Not applicable"))) +
geom_point()
Ein anderer Typ der Umordnung ist nützlich, wenn du die Linien eines Plots farblich einfärbst. Durch die Umsortierung passen sich die Farben des Plots auf der rechten Seite der Legende an.
by_age <- gss_cat |>
filter(!is.na(age)) |>
count(age, marital) |>
group_by(age) |>
mutate(
prop = n / sum(n)
)
ggplot(by_age, aes(x = age, y = prop, color = marital)) +
geom_line(na.rm = TRUE)
ggplot(by_age, aes(x = age, y = prop, color = fct_reorder2(marital, age, prop))) +
geom_line() +
labs(color = "marital")
Für Balkendiagramme kannst du fct_infreq() benutzen, um
Levels Nach Häufigkeit zu sortieren. Kombiniere es mit
fct_rev(), wenn du den häufigsten Wert auf der rechten,
statt auf der linken Seite haben willst.
gss_cat |>
mutate(marital = marital |> fct_infreq() |> fct_rev()) |>
ggplot(aes(x = marital)) +
geom_bar()
Mächtiger als die Reihenfolge zu verändern ist es, ihre Werte zu
wechseln. Das mächtigste Werkzeug ist fct_recode(). Es
wechselt den Wert von jedem Level.
gss_cat |> count(partyid)
## # A tibble: 10 × 2
## partyid n
## <fct> <int>
## 1 No answer 154
## 2 Don't know 1
## 3 Other party 393
## 4 Strong republican 2314
## 5 Not str republican 3032
## 6 Ind,near rep 1791
## 7 Independent 4119
## 8 Ind,near dem 2499
## 9 Not str democrat 3690
## 10 Strong democrat 3490
Die Level sind lapidar und inkonsistent. Die neuen Werte stehen auf der linken und die alten Werte auf der rechten Seite.
gss_cat |>
mutate(
partyid = fct_recode(partyid,
"Republican, strong" = "Strong republican",
"Republican, weak" = "Not str republican",
"Independent, near rep" = "Ind,near rep",
"Independent, near dem" = "Ind,near dem",
"Democrat, weak" = "Not str democrat",
"Democrat, strong" = "Strong democrat"
)
) |>
count(partyid)
## # A tibble: 10 × 2
## partyid n
## <fct> <int>
## 1 No answer 154
## 2 Don't know 1
## 3 Other party 393
## 4 Republican, strong 2314
## 5 Republican, weak 3032
## 6 Independent, near rep 1791
## 7 Independent 4119
## 8 Independent, near dem 2499
## 9 Democrat, weak 3690
## 10 Democrat, strong 3490
Um Gruppen zu kombinieren, kannst du einfach verschiedenen alten Level, gleiche neue verpassen.
gss_cat |>
mutate(
partyid = fct_recode(partyid,
"Republican, strong" = "Strong republican",
"Republican, weak" = "Not str republican",
"Independent, near rep" = "Ind,near rep",
"Independent, near dem" = "Ind,near dem",
"Democrat, weak" = "Not str democrat",
"Democrat, strong" = "Strong democrat",
"Other" = "No answer",
"Other" = "Don't know",
"Other" = "Other party"
)
) |>
count(partyid)
## # A tibble: 8 × 2
## partyid n
## <fct> <int>
## 1 Other 548
## 2 Republican, strong 2314
## 3 Republican, weak 3032
## 4 Independent, near rep 1791
## 5 Independent 4119
## 6 Independent, near dem 2499
## 7 Democrat, weak 3690
## 8 Democrat, strong 3490
fct_collapse() ist eine Variante, wenn du sehr viele
Level zusammenfassen willst.
gss_cat |>
mutate(
partyid = fct_collapse(partyid,
"other" = c("No answer", "Don't know", "Other party"),
"rep" = c("Strong republican", "Not str republican"),
"ind" = c("Ind,near rep", "Independent", "Ind,near dem"),
"dem" = c("Not str democrat", "Strong democrat")
)
) |>
count(partyid)
## # A tibble: 4 × 2
## partyid n
## <fct> <int>
## 1 other 548
## 2 rep 5346
## 3 ind 8409
## 4 dem 7180
Manchmal willst du kleine Gruppen zusammenwerfen. Diesen Job macht
fct_lump_*(). fct_lump_lowfreq() schmeißt die
kleinsten Gruppen in eine “Other” Kategorie.
gss_cat |>
mutate(relig = fct_lump_lowfreq(relig)) |>
count(relig)
## # A tibble: 2 × 2
## relig n
## <fct> <int>
## 1 Protestant 10846
## 2 Other 10637
Ein bisschen mehr Details wollen wir schon sehen! Wir wollen genau
n=10 Gruppen sehen, mit fct_lump_n().
gss_cat |>
mutate(relig = fct_lump_n(relig, n = 10)) |>
count(relig, sort = TRUE) |>
print(n = Inf)
## # A tibble: 10 × 2
## relig n
## <fct> <int>
## 1 Protestant 10846
## 2 Catholic 5124
## 3 None 3523
## 4 Christian 689
## 5 Other 458
## 6 Jewish 388
## 7 Buddhism 147
## 8 Inter-nondenominational 109
## 9 Moslem/islam 104
## 10 Orthodox-christian 95
Wir konzentrieren uns auf das lubridate Paket.
library(tidyverse)
library(lubridate)
library(nycflights13)
Drei Typen von date/time beziehen sich auf den Moment der Zeit:
date - Tibble als <date>
time - Tibble als <time>
date-time - Tibble als <dttm>
Für das aktuelle Datum oder date-time, nutze today()
oder now():
today()
## [1] "2023-10-15"
now()
## [1] "2023-10-15 14:55:55 CEST"
Die folgenden Abschnitte beschreiben vier weitere Möglichkeiten.
csv <- "
date,datetime
2022-01-02,2022-01-02 05:12
"
read_csv(csv)
## Rows: 1 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dttm (1): datetime
## date (1): date
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
## # A tibble: 1 × 2
## date datetime
## <date> <dttm>
## 1 2022-01-02 2022-01-02 05:12:00
Das Ausgabeformat kannst du individuell bestimmen.
csv <- "
date
01/02/15
"
read_csv(csv, col_types = cols(date = col_date("%m/%d/%y")))
## # A tibble: 1 × 1
## date
## <date>
## 1 2015-01-02
read_csv(csv, col_types = cols(date = col_date("%d/%m/%y")))
## # A tibble: 1 × 1
## date
## <date>
## 1 2015-02-01
read_csv(csv, col_types = cols(date = col_date("%y/%m/%d")))
## # A tibble: 1 × 1
## date
## <date>
## 1 2001-02-15
ymd("2017-01-31")
## [1] "2017-01-31"
mdy("January 31st, 2017")
## [1] "2017-01-31"
dmy("31-Jan-2017")
## [1] "2017-01-31"
Um ein date-time zu kreieren, füge _ und mind. “h”, “m” oder “s” hinzu:
ymd_hms("2017-01-31 20:11:59")
#> [1] "2017-01-31 20:11:59 UTC"
mdy_hm("01/31/2017 08:01")
#> [1] "2017-01-31 08:01:00 UTC"
flights |>
select(year, month, day, hour, minute)
## # A tibble: 336,776 × 5
## year month day hour minute
## <int> <int> <int> <dbl> <dbl>
## 1 2013 1 1 5 15
## 2 2013 1 1 5 29
## 3 2013 1 1 5 40
## 4 2013 1 1 5 45
## 5 2013 1 1 6 0
## 6 2013 1 1 5 58
## 7 2013 1 1 6 0
## 8 2013 1 1 6 0
## 9 2013 1 1 6 0
## 10 2013 1 1 6 0
## # ℹ 336,766 more rows
flights |>
select(year, month, day, hour, minute) |>
mutate(departure = make_datetime(year, month, day, hour, minute))
## # A tibble: 336,776 × 6
## year month day hour minute departure
## <int> <int> <int> <dbl> <dbl> <dttm>
## 1 2013 1 1 5 15 2013-01-01 05:15:00
## 2 2013 1 1 5 29 2013-01-01 05:29:00
## 3 2013 1 1 5 40 2013-01-01 05:40:00
## 4 2013 1 1 5 45 2013-01-01 05:45:00
## 5 2013 1 1 6 0 2013-01-01 06:00:00
## 6 2013 1 1 5 58 2013-01-01 05:58:00
## 7 2013 1 1 6 0 2013-01-01 06:00:00
## 8 2013 1 1 6 0 2013-01-01 06:00:00
## 9 2013 1 1 6 0 2013-01-01 06:00:00
## 10 2013 1 1 6 0 2013-01-01 06:00:00
## # ℹ 336,766 more rows
Ein paar Basics haben wir schon kennengelernt, jetzt gehen wir ins Detail.
Die meisten Funktionen kommen von dplyr und tidyr, die Teil des tidyverse sind.
library(tidyverse)
Ein paar handliche Werkzeuge, um Missing Values zu kreieren
oder zu eliminieren. Zellen mit NA.
Manchmal zeigt ein NA, dass der Wert der vorangehenden
Zeile wiederholt wurde.
treatment <- tribble(
~person, ~treatment, ~response,
"Derrick Whitmore", 1, 7,
NA, 2, 10,
NA, 3, NA,
"Katherine Burke", 1, 4
)
Diese NA kann man füllen mit tidyr::fill().
Es nimmt eine Menge von Spalten auf.
treatment |>
fill(everything())
## # A tibble: 4 × 3
## person treatment response
## <chr> <dbl> <dbl>
## 1 Derrick Whitmore 1 7
## 2 Derrick Whitmore 2 10
## 3 Derrick Whitmore 3 10
## 4 Katherine Burke 1 4
Manchmal werden NA’s ersetzt durch feste, bekannte Werte
wie 0. Benutze dplyr:: coalesce(), um die NA’s
zu ersetzen:
x <- c(1, 4, 5, 7, NA)
coalesce(x, 0)
## [1] 1 4 5 7 0
Oft wird eine Zahl wie die -99 als NA geschrieben.
Ersetze sie durch NA mit der Funktion dplyr::na_if():
x <- c(1, 4, 5, 7, -99)
na_if(x, -99)
## [1] 1 4 5 7 NA
Ein spezieller Typ von Missing Values ist NaN,
er verhält sich wie NA:
x <- c(NA, NaN)
x * 10
## [1] NA NaN
x == 1
## [1] NA NA
is.na(x)
## [1] TRUE TRUE
NaN trifft häufig bei mathematischen Operationen
auf:
0 / 0
## [1] NaN
0 * Inf
## [1] NaN
Inf - Inf
## [1] NaN
Explizite Missing Values kannst du durch
ein NA lokalisieren. Implizierte Missing Values
zeichnen sich dadurch aus, dass ganze Zeilen oder Spalten fehlen.
stocks <- tibble(
year = c(2020, 2020, 2020, 2020, 2021, 2021, 2021),
qtr = c( 1, 2, 3, 4, 2, 3, 4),
price = c(1.88, 0.59, 0.35, NA, 0.92, 0.17, 2.66)
)
Ein Werkzeug kann implizierte Missings explizit machen, und umgekehrt: Pivoting. In unserem Beispiel muss jede Kombination von Zeilen und Spalten einen Wert haben.
stocks |>
pivot_wider(
names_from = qtr,
values_from = price
)
## # A tibble: 2 × 5
## year `1` `2` `3` `4`
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2020 1.88 0.59 0.35 NA
## 2 2021 NA 0.92 0.17 2.66
Completetidyr::complete() generiert explizite Missings,
durch das Anbieten der Variablen, die die Kombinationen von Zeilen
definieren, die existieren sollten. Zum Beispiel wissen wir, dass alle
Kombinationen von year und qtr in
stocks existieren sollten.
stocks |>
complete(year, qtr)
## # A tibble: 8 × 3
## year qtr price
## <dbl> <dbl> <dbl>
## 1 2020 1 1.88
## 2 2020 2 0.59
## 3 2020 3 0.35
## 4 2020 4 NA
## 5 2021 1 NA
## 6 2021 2 0.92
## 7 2021 3 0.17
## 8 2021 4 2.66
Du rufst complete() mit den existierenden Variablen auf,
so dass die fehlenden Kombinationen aufgefüllt werden. Sind die
Variablen selbst nicht komplett, kannst du eigene Werte liefern. So
kannst du den stocks Datensatz von 2019 bis 2021 laufen
lassen.
stocks |>
complete(year = 2019:2021, qtr)
## # A tibble: 12 × 3
## year qtr price
## <dbl> <dbl> <dbl>
## 1 2019 1 NA
## 2 2019 2 NA
## 3 2019 3 NA
## 4 2019 4 NA
## 5 2020 1 1.88
## 6 2020 2 0.59
## 7 2020 3 0.35
## 8 2020 4 NA
## 9 2021 1 NA
## 10 2021 2 0.92
## 11 2021 3 0.17
## 12 2021 4 2.66
Benutze full_seq(x, 1), um fehlende Lücken zu stopfen,
hier in Einer-Schritten.
full_seq(c(1, 2, 4, 5, 10), 1)
## [1] 1 2 3 4 5 6 7 8 9 10
Joins lernen wir später näher kennen.
dplyr::anti_join(x, y) nimmt nur die Reihen in
x, die kein Match in y haben. So können wir
enthüllen, dass Informationen zu vier Flughäfen fehlen, die in
flights genannt wurden.
library(nycflights13)
flights |>
distinct(faa = dest) |>
anti_join(airports)
## Joining with `by = join_by(faa)`
## # A tibble: 4 × 1
## faa
## <chr>
## 1 BQN
## 2 SJU
## 3 STT
## 4 PSE
Zuerst werden alle verschiedenen dest in faa
gespeichert. Mit anti_join(airports) wird dann in der
Variable faa in dem Datensatz airports
geschaut, ob die dest dort zu finden sind. In unserem Fall
fehlen dort vier Flughäfen.
flights |>
distinct(tailnum) |>
anti_join(planes)
## Joining with `by = join_by(tailnum)`
## # A tibble: 722 × 1
## tailnum
## <chr>
## 1 N3ALAA
## 2 N3DUAA
## 3 N542MQ
## 4 N730MQ
## 5 N9EAMQ
## 6 N532UA
## 7 N3EMAA
## 8 N518MQ
## 9 N3BAAA
## 10 N3CYAA
## # ℹ 712 more rows
health <- tibble(
name = c("Ikaia", "Oletta", "Leriah", "Dashay", "Tresaun"),
smoker = factor(c("no", "no", "no", "no", "no"), levels = c("yes", "no")),
age = c(34L, 88L, 75L, 47L, 56L),
)
Wir wollen erstmal die Anzahl an Rauchern zählen mit
dplyr::count().
health |> count(smoker)
## # A tibble: 1 × 2
## smoker n
## <fct> <int>
## 1 no 5
Da die Gruppe nur Nichtraucher enthält, werden keine Nicht-Raucher
angezeigt. Wir können aber nach diesen nachfragen, auch wenn keine
vorhanden sind, mit .drop = FALSE:
health |> count(smoker, .drop = FALSE)
## # A tibble: 2 × 2
## smoker n
## <fct> <int>
## 1 yes 0
## 2 no 5
Gleiches mit Achsen:
ggplot(health, aes(x = smoker)) +
geom_bar() +
scale_x_discrete()
ggplot(health, aes(x = smoker)) +
geom_bar() +
scale_x_discrete(drop = FALSE)
Gleiches Problem mit dplyr::group_by():
health |>
group_by(smoker, .drop = FALSE) |>
summarize(
n = n(),
mean_age = mean(age),
min_age = min(age),
max_age = max(age),
sd_age = sd(age)
)
## Warning: There were 2 warnings in `summarize()`.
## The first warning was:
## ℹ In argument: `min_age = min(age)`.
## ℹ In group 1: `smoker = yes`.
## Caused by warning in `min()`:
## ! kein nicht-fehlendes Argument für min; gebe Inf zurück
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 1 remaining warning.
## # A tibble: 2 × 6
## smoker n mean_age min_age max_age sd_age
## <fct> <int> <dbl> <dbl> <dbl> <dbl>
## 1 yes 0 NaN Inf -Inf NA
## 2 no 5 60 34 88 21.6
Ein leerer Vektor hat die Länge 0, Missing Values haben
jeweils die Länge 1.
Führe Summary durch und dann mache die implizierten Missings
explizit mit complete().
health |>
group_by(smoker) |>
summarize(
n = n(),
mean_age = mean(age),
min_age = min(age),
max_age = max(age),
sd_age = sd(age)
) |>
complete(smoker)
## # A tibble: 2 × 6
## smoker n mean_age min_age max_age sd_age
## <fct> <int> <dbl> <int> <int> <dbl>
## 1 yes NA NA NA NA NA
## 2 no 5 60 34 88 21.6
Es ist selten, dass du nur einen einzigen Data Frame hast. Normalerweise hast du mehrere und du musst sie “joinen”, also zusammenfügen. Es gibt zwei Arten von joins:
library(tidyverse)
library(nycflights13)
Zuerst müssen wir verstehen, wie zwei Tabellen miteinander verbunden werden können. Dies durch ein Paar von Schlüsseln, die sich in jeder Tabelle befinden.
Jedes “Join” involviert ein Paar von Schlüsseln: einen Primärschlüssel und einen Fremdschlüssel. Ein Primärschlüssel ist eine Variable, die jede Beobachtung eindeutig identifiziert. compound key sind mehr als eine Variable.
In airlines finden wir zwei Variablen zu jeder Airline:
carrier code und name. Identifizieren lässt sich jede
Airline eindeutig durch den carrier code, so dass
carrier der Primärschlüssel ist.
airlines
## # A tibble: 16 × 2
## carrier name
## <chr> <chr>
## 1 9E Endeavor Air Inc.
## 2 AA American Airlines Inc.
## 3 AS Alaska Airlines Inc.
## 4 B6 JetBlue Airways
## 5 DL Delta Air Lines Inc.
## 6 EV ExpressJet Airlines Inc.
## 7 F9 Frontier Airlines Inc.
## 8 FL AirTran Airways Corporation
## 9 HA Hawaiian Airlines Inc.
## 10 MQ Envoy Air
## 11 OO SkyWest Airlines Inc.
## 12 UA United Air Lines Inc.
## 13 US US Airways Inc.
## 14 VX Virgin America
## 15 WN Southwest Airlines Co.
## 16 YV Mesa Airlines Inc.
In airports kann jeder Flughafen durch seinen
airport code identifiziert werden: faa.
airports
## # A tibble: 1,458 × 8
## faa name lat lon alt tz dst tzone
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
## 1 04G Lansdowne Airport 41.1 -80.6 1044 -5 A America/…
## 2 06A Moton Field Municipal Airport 32.5 -85.7 264 -6 A America/…
## 3 06C Schaumburg Regional 42.0 -88.1 801 -6 A America/…
## 4 06N Randall Airport 41.4 -74.4 523 -5 A America/…
## 5 09J Jekyll Island Airport 31.1 -81.4 11 -5 A America/…
## 6 0A9 Elizabethton Municipal Airport 36.4 -82.2 1593 -5 A America/…
## 7 0G6 Williams County Airport 41.5 -84.5 730 -5 A America/…
## 8 0G7 Finger Lakes Regional Airport 42.9 -76.8 492 -5 A America/…
## 9 0P2 Shoestring Aviation Airfield 39.8 -76.6 1000 -5 U America/…
## 10 0S9 Jefferson County Intl 48.1 -123. 108 -8 A America/…
## # ℹ 1,448 more rows
In planes finden wir Infos zu jedem Flugzeug, das durch
tailnum eindeutig identifiziert werden kann.
planes
## # A tibble: 3,322 × 9
## tailnum year type manufacturer model engines seats speed engine
## <chr> <int> <chr> <chr> <chr> <int> <int> <int> <chr>
## 1 N10156 2004 Fixed wing multi… EMBRAER EMB-… 2 55 NA Turbo…
## 2 N102UW 1998 Fixed wing multi… AIRBUS INDU… A320… 2 182 NA Turbo…
## 3 N103US 1999 Fixed wing multi… AIRBUS INDU… A320… 2 182 NA Turbo…
## 4 N104UW 1999 Fixed wing multi… AIRBUS INDU… A320… 2 182 NA Turbo…
## 5 N10575 2002 Fixed wing multi… EMBRAER EMB-… 2 55 NA Turbo…
## 6 N105UW 1999 Fixed wing multi… AIRBUS INDU… A320… 2 182 NA Turbo…
## 7 N107US 1999 Fixed wing multi… AIRBUS INDU… A320… 2 182 NA Turbo…
## 8 N108UW 1999 Fixed wing multi… AIRBUS INDU… A320… 2 182 NA Turbo…
## 9 N109UW 1999 Fixed wing multi… AIRBUS INDU… A320… 2 182 NA Turbo…
## 10 N110UW 1999 Fixed wing multi… AIRBUS INDU… A320… 2 182 NA Turbo…
## # ℹ 3,312 more rows
weather liefert Daten zum Wetter an den Flughäfen.
origin und time_hour sind also hier der
compound Primärschlüssel.
weather
## # A tibble: 26,115 × 15
## origin year month day hour temp dewp humid wind_dir wind_speed
## <chr> <int> <int> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 EWR 2013 1 1 1 39.0 26.1 59.4 270 10.4
## 2 EWR 2013 1 1 2 39.0 27.0 61.6 250 8.06
## 3 EWR 2013 1 1 3 39.0 28.0 64.4 240 11.5
## 4 EWR 2013 1 1 4 39.9 28.0 62.2 250 12.7
## 5 EWR 2013 1 1 5 39.0 28.0 64.4 260 12.7
## 6 EWR 2013 1 1 6 37.9 28.0 67.2 240 11.5
## 7 EWR 2013 1 1 7 39.0 28.0 64.4 240 15.0
## 8 EWR 2013 1 1 8 39.9 28.0 62.2 250 10.4
## 9 EWR 2013 1 1 9 39.9 28.0 62.2 260 15.0
## 10 EWR 2013 1 1 10 41 28.0 59.6 260 13.8
## # ℹ 26,105 more rows
## # ℹ 5 more variables: wind_gust <dbl>, precip <dbl>, pressure <dbl>,
## # visib <dbl>, time_hour <dttm>
Ein Fremdschlüssel ist eine Variable (oder mehrere), die zu mit einem anderen Primärschlüssel in einer anderen Tabelle korrespondiert:
flights$tailnum ist Fremdschlüssel, der mit
Primärschlüssel planes$tailnum korrespondiert.
flights$carrier zu
airlines$carrier.
flights$origin zu
airports$faa.
flights$dest zu airports$faa.
flights$origin-flights$time_hour compound
Fremdschlüssel, der mit compound Primärschlüssel
weather$origin-weather$time_hour korrespondiert.
Sind sie wirklich einmalig, also die Beobachtungen?
planes |>
count(tailnum) |>
filter(n > 1)
## # A tibble: 0 × 2
## # ℹ 2 variables: tailnum <chr>, n <int>
weather |>
count(time_hour, origin) |>
filter(n > 1)
## # A tibble: 0 × 3
## # ℹ 3 variables: time_hour <dttm>, origin <chr>, n <int>
Checke auch nach Missing Values!
planes |>
filter(is.na(tailnum))
## # A tibble: 0 × 9
## # ℹ 9 variables: tailnum <chr>, year <int>, type <chr>, manufacturer <chr>,
## # model <chr>, engines <int>, seats <int>, speed <int>, engine <chr>
weather |>
filter(is.na(time_hour) | is.na(origin))
## # A tibble: 0 × 15
## # ℹ 15 variables: origin <chr>, year <int>, month <int>, day <int>, hour <int>,
## # temp <dbl>, dewp <dbl>, humid <dbl>, wind_dir <dbl>, wind_speed <dbl>,
## # wind_gust <dbl>, precip <dbl>, pressure <dbl>, visib <dbl>,
## # time_hour <dttm>
Welches ist eigentlich der Primärschlüssel für flights?
Es gibt drei Variablen, die zusammen jeden Flug eindeutig
identifizieren:
flights |>
count(time_hour, carrier, flight) |>
filter(n > 1)
## # A tibble: 0 × 4
## # ℹ 4 variables: time_hour <dttm>, carrier <chr>, flight <int>, n <int>
Macht es die Abwesenheit von Duplikaten gleich zu einem guten Primärschlüssel? Ich denke nicht. Hier aber schon, da es irritierend für eine Fluglinie wäre, würden mehrere Flugzeuge mit derselben Flugnummer zur selben Zeit abheben.
Abgesehen davon, können wir einen Surrogat-Schlüssel durch die Zeilennummer konstruieren.
flights2 <- flights |>
mutate(id = row_number(), .before = 1)
flights2
## # A tibble: 336,776 × 20
## id year month day dep_time sched_dep_time dep_delay arr_time
## <int> <int> <int> <int> <int> <int> <dbl> <int>
## 1 1 2013 1 1 517 515 2 830
## 2 2 2013 1 1 533 529 4 850
## 3 3 2013 1 1 542 540 2 923
## 4 4 2013 1 1 544 545 -1 1004
## 5 5 2013 1 1 554 600 -6 812
## 6 6 2013 1 1 554 558 -4 740
## 7 7 2013 1 1 555 600 -5 913
## 8 8 2013 1 1 557 600 -3 709
## 9 9 2013 1 1 557 600 -3 838
## 10 10 2013 1 1 558 600 -2 753
## # ℹ 336,766 more rows
## # ℹ 12 more variables: sched_arr_time <int>, arr_delay <dbl>, carrier <chr>,
## # flight <int>, tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>,
## # distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>
Bei der Kommunikation zu einem anderen ist es einfach einfacher auf Flug 2001 zu verweisen, als auf Flug UA43, Abflugzeit 9 Uhr, am 21.3.2006.
dplyr bietet sechs join Funktionen an:
left, inner, right, full, semi, anti.
Erlaubt uns Variablen von zwei Data Frames zu kombinieren. Erst werden die Beobachtungen nach den Schlüsseln gematcht. Dann von einem Data Frame Variablen in den anderen kopiert. Die neuen Variablen werden rechts eingeordnet, so dass sie in der Console nicht unbedingt sichtbar sind. Wir bauen uns ein Dataset mit sechs Variablen.
flights2 <- flights |>
select(year, time_hour, origin, dest, tailnum, carrier)
flights2
## # A tibble: 336,776 × 6
## year time_hour origin dest tailnum carrier
## <int> <dttm> <chr> <chr> <chr> <chr>
## 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA
## 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA
## 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA
## 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6
## 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL
## 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA
## 7 2013 2013-01-01 06:00:00 EWR FLL N516JB B6
## 8 2013 2013-01-01 06:00:00 LGA IAD N829AS EV
## 9 2013 2013-01-01 06:00:00 JFK MCO N593JB B6
## 10 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA
## # ℹ 336,766 more rows
Meist benutzt man den left_join(). Der Output hat immer
dieselbe Zeilenanzahl wie x. Der primäre Nutzen ist es
Metadaten hinzuzufügen. So können wir den kompletten Airline Namen zu
flights2 hinzufügen.
flights2 |>
left_join(airlines)
## Joining with `by = join_by(carrier)`
## # A tibble: 336,776 × 7
## year time_hour origin dest tailnum carrier name
## <int> <dttm> <chr> <chr> <chr> <chr> <chr>
## 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA United Air Lines Inc.
## 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA United Air Lines Inc.
## 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA American Airlines Inc.
## 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 JetBlue Airways
## 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Delta Air Lines Inc.
## 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA United Air Lines Inc.
## 7 2013 2013-01-01 06:00:00 EWR FLL N516JB B6 JetBlue Airways
## 8 2013 2013-01-01 06:00:00 LGA IAD N829AS EV ExpressJet Airlines I…
## 9 2013 2013-01-01 06:00:00 JFK MCO N593JB B6 JetBlue Airways
## 10 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA American Airlines Inc.
## # ℹ 336,766 more rows
Oder Wetterdaten zu den Abflugzeiten finden.
flights2 |>
left_join(weather |> select(origin, time_hour, temp, wind_speed))
## Joining with `by = join_by(time_hour, origin)`
## # A tibble: 336,776 × 8
## year time_hour origin dest tailnum carrier temp wind_speed
## <int> <dttm> <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA 39.0 12.7
## 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA 39.9 15.0
## 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA 39.0 15.0
## 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 39.0 15.0
## 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL 39.9 16.1
## 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA 39.0 12.7
## 7 2013 2013-01-01 06:00:00 EWR FLL N516JB B6 37.9 11.5
## 8 2013 2013-01-01 06:00:00 LGA IAD N829AS EV 39.9 16.1
## 9 2013 2013-01-01 06:00:00 JFK MCO N593JB B6 37.9 13.8
## 10 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA 39.9 16.1
## # ℹ 336,766 more rows
Die Flugzeuggröße.
flights2 |>
left_join(planes |> select(tailnum, type, engines, seats))
## Joining with `by = join_by(tailnum)`
## # A tibble: 336,776 × 9
## year time_hour origin dest tailnum carrier type engines seats
## <int> <dttm> <chr> <chr> <chr> <chr> <chr> <int> <int>
## 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA Fixed w… 2 149
## 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA Fixed w… 2 149
## 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA Fixed w… 2 178
## 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 Fixed w… 2 200
## 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Fixed w… 2 178
## 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Fixed w… 2 191
## 7 2013 2013-01-01 06:00:00 EWR FLL N516JB B6 Fixed w… 2 200
## 8 2013 2013-01-01 06:00:00 LGA IAD N829AS EV Fixed w… 2 55
## 9 2013 2013-01-01 06:00:00 JFK MCO N593JB B6 Fixed w… 2 200
## 10 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
## # ℹ 336,766 more rows
Findet left_join() kein Match für eine Reihe in
x, so wird mit NA aufgefüllt.
flights2 |>
filter(tailnum == "N3ALAA") |>
left_join(planes |> select(tailnum, type, engines, seats))
## Joining with `by = join_by(tailnum)`
## # A tibble: 63 × 9
## year time_hour origin dest tailnum carrier type engines seats
## <int> <dttm> <chr> <chr> <chr> <chr> <chr> <int> <int>
## 1 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
## 2 2013 2013-01-02 18:00:00 LGA ORD N3ALAA AA <NA> NA NA
## 3 2013 2013-01-03 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
## 4 2013 2013-01-07 19:00:00 LGA ORD N3ALAA AA <NA> NA NA
## 5 2013 2013-01-08 17:00:00 JFK ORD N3ALAA AA <NA> NA NA
## 6 2013 2013-01-16 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
## 7 2013 2013-01-20 18:00:00 LGA ORD N3ALAA AA <NA> NA NA
## 8 2013 2013-01-22 17:00:00 JFK ORD N3ALAA AA <NA> NA NA
## 9 2013 2013-10-11 06:00:00 EWR MIA N3ALAA AA <NA> NA NA
## 10 2013 2013-10-14 08:00:00 JFK BOS N3ALAA AA <NA> NA NA
## # ℹ 53 more rows
left_join() benutzt immer Variablen, die in beiden
Data Frames auftauchen als Join Key: natural
join. Manchmal funktioniert es nicht. So können Variablen
mit gleichem Namen in verschiedenen Datensätzen eine andere Bedeutung
haben.
flights2 |>
left_join(planes)
## Joining with `by = join_by(year, tailnum)`
## # A tibble: 336,776 × 13
## year time_hour origin dest tailnum carrier type manufacturer
## <int> <dttm> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA <NA> <NA>
## 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA <NA> <NA>
## 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA <NA> <NA>
## 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 <NA> <NA>
## 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL <NA> <NA>
## 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA <NA> <NA>
## 7 2013 2013-01-01 06:00:00 EWR FLL N516JB B6 <NA> <NA>
## 8 2013 2013-01-01 06:00:00 LGA IAD N829AS EV <NA> <NA>
## 9 2013 2013-01-01 06:00:00 JFK MCO N593JB B6 <NA> <NA>
## 10 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA <NA> <NA>
## # ℹ 336,766 more rows
## # ℹ 5 more variables: model <chr>, engines <int>, seats <int>, speed <int>,
## # engine <chr>
Hier hat year in beiden Datensätzen eine andere
Bedeutung. flight$year ist das Jahr, in dem der Flug
stattgefunden hat. planes$year ist das Jahr, in dem das
Flugzeug gebaut wurde. Wir wollen aber nur auf tailnum
joinen, also müssen wir eine explizite Spezifiation anbieten, mit
join_by().
flights2 |>
# left_join(planes, join_by(tailnum)) muss noch aktualisiert werden
left_join(planes, by = "tailnum")
## # A tibble: 336,776 × 14
## year.x time_hour origin dest tailnum carrier year.y type
## <int> <dttm> <chr> <chr> <chr> <chr> <int> <chr>
## 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA 1999 Fixed wing mu…
## 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA 1998 Fixed wing mu…
## 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA 1990 Fixed wing mu…
## 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 2012 Fixed wing mu…
## 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL 1991 Fixed wing mu…
## 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA 2012 Fixed wing mu…
## 7 2013 2013-01-01 06:00:00 EWR FLL N516JB B6 2000 Fixed wing mu…
## 8 2013 2013-01-01 06:00:00 LGA IAD N829AS EV 1998 Fixed wing mu…
## 9 2013 2013-01-01 06:00:00 JFK MCO N593JB B6 2004 Fixed wing mu…
## 10 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA NA <NA>
## # ℹ 336,766 more rows
## # ℹ 6 more variables: manufacturer <chr>, model <chr>, engines <int>,
## # seats <int>, speed <int>, engine <chr>
Die Variablen year wurden im gemeinsamen Datensatz jetzt
optisch eindeutig gemacht, mit einem Zusatz (year.x,
year.y), der genau sagt, wo die Variable herkommt: von
x oder von y. Du kannst das Suffix natürlich
überschreiben.
by = "tailnum" steht kurz für
by = c("tailnum" = "tailnum") (evtl.
join_by(tailnum) für
join_by(tailnum == tailnum)).
Es gibt zwei Möglichkeiten die flights2 und die
airports Tabelle zu verbinden: über dest oder
über origin.
flights2 |>
left_join(airports, by = c("dest" = "faa"))
## # A tibble: 336,776 × 13
## year time_hour origin dest tailnum carrier name lat lon
## <int> <dttm> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA George Bu… 30.0 -95.3
## 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA George Bu… 30.0 -95.3
## 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA Miami Intl 25.8 -80.3
## 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 <NA> NA NA
## 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Hartsfiel… 33.6 -84.4
## 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Chicago O… 42.0 -87.9
## 7 2013 2013-01-01 06:00:00 EWR FLL N516JB B6 Fort Laud… 26.1 -80.2
## 8 2013 2013-01-01 06:00:00 LGA IAD N829AS EV Washingto… 38.9 -77.5
## 9 2013 2013-01-01 06:00:00 JFK MCO N593JB B6 Orlando I… 28.4 -81.3
## 10 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA Chicago O… 42.0 -87.9
## # ℹ 336,766 more rows
## # ℹ 4 more variables: alt <dbl>, tz <dbl>, dst <chr>, tzone <chr>
flights2 |>
left_join(airports, by = c("origin" = "faa"))
## # A tibble: 336,776 × 13
## year time_hour origin dest tailnum carrier name lat lon
## <int> <dttm> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA Newark Li… 40.7 -74.2
## 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA La Guardia 40.8 -73.9
## 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA John F Ke… 40.6 -73.8
## 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 John F Ke… 40.6 -73.8
## 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL La Guardia 40.8 -73.9
## 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Newark Li… 40.7 -74.2
## 7 2013 2013-01-01 06:00:00 EWR FLL N516JB B6 Newark Li… 40.7 -74.2
## 8 2013 2013-01-01 06:00:00 LGA IAD N829AS EV La Guardia 40.8 -73.9
## 9 2013 2013-01-01 06:00:00 JFK MCO N593JB B6 John F Ke… 40.6 -73.8
## 10 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA La Guardia 40.8 -73.9
## # ℹ 336,766 more rows
## # ℹ 4 more variables: alt <dbl>, tz <dbl>, dst <chr>, tzone <chr>
Semi-Joins behalten alle Reihen in x,
die ein Match in y haben. So können wir alle Flughäfen
anzeigen, die passenden Origin haben:
airports |>
semi_join(flights2, by = c("faa" = "origin"))
## # A tibble: 3 × 8
## faa name lat lon alt tz dst tzone
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
## 1 EWR Newark Liberty Intl 40.7 -74.2 18 -5 A America/New_York
## 2 JFK John F Kennedy Intl 40.6 -73.8 13 -5 A America/New_York
## 3 LGA La Guardia 40.8 -73.9 22 -5 A America/New_York
Oder natürlich eine passenden Destination.
airports |>
semi_join(flights2, by = c("faa" = "dest"))
## # A tibble: 101 × 8
## faa name lat lon alt tz dst tzone
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
## 1 ABQ Albuquerque International Sunport 35.0 -107. 5355 -7 A Ameri…
## 2 ACK Nantucket Mem 41.3 -70.1 48 -5 A Ameri…
## 3 ALB Albany Intl 42.7 -73.8 285 -5 A Ameri…
## 4 ANC Ted Stevens Anchorage Intl 61.2 -150. 152 -9 A Ameri…
## 5 ATL Hartsfield Jackson Atlanta Intl 33.6 -84.4 1026 -5 A Ameri…
## 6 AUS Austin Bergstrom Intl 30.2 -97.7 542 -6 A Ameri…
## 7 AVL Asheville Regional Airport 35.4 -82.5 2165 -5 A Ameri…
## 8 BDL Bradley Intl 41.9 -72.7 173 -5 A Ameri…
## 9 BGR Bangor Intl 44.8 -68.8 192 -5 A Ameri…
## 10 BHM Birmingham Intl 33.6 -86.8 644 -6 A Ameri…
## # ℹ 91 more rows
Anti-Joins sind das Gegenteil. Sie geben alle Reihen in
x aus, die kein Match in y haben.
flights2 |>
anti_join(airports, by = c("dest" = "faa")) |>
distinct(dest)
## # A tibble: 4 × 1
## dest
## <chr>
## 1 BQN
## 2 SJU
## 3 STT
## 4 PSE
Welche tailnum fehlen in planes? Sie sind
in flights2, aber nicht in planes.
flights2 |>
anti_join(planes, by = "tailnum") |>
distinct(tailnum)
## # A tibble: 722 × 1
## tailnum
## <chr>
## 1 N3ALAA
## 2 N3DUAA
## 3 N542MQ
## 4 N730MQ
## 5 N9EAMQ
## 6 N532UA
## 7 N3EMAA
## 8 N518MQ
## 9 N3BAAA
## 10 N3CYAA
## # ℹ 712 more rows
x <- tribble(
~key, ~val_x,
1, "x1",
2, "x2",
3, "x3"
)
y <- tribble(
~key, ~val_y,
1, "y1",
2, "y2",
4, "y3"
)
x
## # A tibble: 3 × 2
## key val_x
## <dbl> <chr>
## 1 1 x1
## 2 2 x2
## 3 3 x3
y
## # A tibble: 3 × 2
## key val_y
## <dbl> <chr>
## 1 1 y1
## 2 2 y2
## 3 4 y3
Beim Inner Join matchen Reihen, wenn die Schlüssel gleich
sind. Also enthält der Output nur Reihen mit Schlüsseln, die in
x und y enthalten sind.
inner_join(x, y)
## Joining with `by = join_by(key)`
## # A tibble: 2 × 3
## key val_x val_y
## <dbl> <chr> <chr>
## 1 1 x1 y1
## 2 2 x2 y2
Ein Outer Join behält Beobachtungen, die in mindestens einem
der Data Frames auftauchen. Diese Beobachtung hat einen
Schlüsel, der matcht, wenn es kein anderer Schlüssel tut. Ein
NA wird dann erstellt. Drei Outer Joins
existieren:
x. Jede
Reihe von x wird beibehalten im Output.left_join(x, y)
## Joining with `by = join_by(key)`
## # A tibble: 3 × 3
## key val_x val_y
## <dbl> <chr> <chr>
## 1 1 x1 y1
## 2 2 x2 y2
## 3 3 x3 <NA>
y.right_join(x, y)
## Joining with `by = join_by(key)`
## # A tibble: 3 × 3
## key val_x val_y
## <dbl> <chr> <chr>
## 1 1 x1 y1
## 2 2 x2 y2
## 3 4 <NA> y3
x
oder y anfallen. Jede Reihe von x und
y ist im Output vorhanden. Der Output fängt mit allen
Reihen von x an, dann folgen die verbliebenden von
y.full_join(x, y)
## Joining with `by = join_by(key)`
## # A tibble: 4 × 3
## key val_x val_y
## <dbl> <chr> <chr>
## 1 1 x1 y1
## 2 2 x2 y2
## 3 3 x3 <NA>
## 4 4 <NA> y3
Was passiert, wenn eine Reihe in x zu mehr als einer
Reihe in y matcht?
Es kann sein, dass eine Reihe in x:
y matcht. Sie wird
dupliziert.dplyr warnt uns, wann immer es multiple Matches gibt.
df1 <- tibble(key = c(1, 2, 3), val_x = c("x1", "x2", "x3"))
df2 <- tibble(key = c(1, 2, 4), val_y = c("y1", "y2", "y3"))
df1
## # A tibble: 3 × 2
## key val_x
## <dbl> <chr>
## 1 1 x1
## 2 2 x2
## 3 3 x3
df2
## # A tibble: 3 × 2
## key val_y
## <dbl> <chr>
## 1 1 y1
## 2 2 y2
## 3 4 y3
df1 |>
inner_join(df2, by = "key")
## # A tibble: 2 × 3
## key val_x val_y
## <dbl> <chr> <chr>
## 1 1 x1 y1
## 2 2 x2 y2
In unserem eraten Abschnitt haben wir schon Funktionen eingeführt. Hier noch ein wenig intensiver. Funktionen erlauben es dir gewöhnliche Aufgaben zu automatisieren. Gegenüber copy-paste hat es drei Vorteile:
Verpasse deiner Funktion einen Namen, so dass er einfacher zu verstehen ist.
Verändern sich Voraussetzungen, so musst du Code nur an einem Ort aktualisieren, statt an vielen.
Du verringerst die Chance gleiche Fehler wiederholt zu machen.
In diesem Abschnitt lernen wir drei Typen von Funktionen kennen:
Vektor-Funktionen, die einen oder mehrere Vektoren als Input nehmen und einen Vektor als Output ausgeben.
Data Frame Funktionen, die einen Data Frame als Input nehmen und einen Data Frame als Output ausgeben.
Plot-Funktionen, die einen Data Frame als Input nehmen und einen Plot als Output.
library(tidyverse)
library(nycflights13)
df <- tibble(
a = rnorm(5),
b = rnorm(5),
c = rnorm(5),
d = rnorm(5),
)
df
## # A tibble: 5 × 4
## a b c d
## <dbl> <dbl> <dbl> <dbl>
## 1 0.428 0.549 0.672 -1.14
## 2 0.177 -1.55 1.16 -0.960
## 3 -0.0642 -0.144 0.171 -1.33
## 4 -1.69 -3.31 1.09 1.09
## 5 0.964 2.27 -0.212 -0.0656
df |> mutate(
a = (a - min(a, na.rm = TRUE)) /
(max(a, na.rm = TRUE) - min(a, na.rm = TRUE)),
b = (b - min(b, na.rm = TRUE)) /
(max(b, na.rm = TRUE) - min(a, na.rm = TRUE)),
c = (c - min(c, na.rm = TRUE)) /
(max(c, na.rm = TRUE) - min(c, na.rm = TRUE)),
d = (d - min(d, na.rm = TRUE)) /
(max(d, na.rm = TRUE) - min(d, na.rm = TRUE)),
)
## # A tibble: 5 × 4
## a b c d
## <dbl> <dbl> <dbl> <dbl>
## 1 0.798 1.70 0.643 0.0773
## 2 0.703 0.775 1 0.153
## 3 0.612 1.39 0.279 0
## 4 0 0 0.948 1
## 5 1 2.46 0 0.523
Alle Vektoren sollten zwischen 0 und 1 liegen. Tun sie aber nicht.
Copy-Paste hat für b zu einem Fehler geführt. Also besser
Funktionen schreiben.
Welche Teile des Codes sind jetzt konstant und welche variieren?
(a - min(a, na.rm = TRUE)) / (max(a, na.rm = TRUE) - min(a, na.rm = TRUE))
(b - min(b, na.rm = TRUE)) / (max(b, na.rm = TRUE) - min(b, na.rm = TRUE))
(c - min(c, na.rm = TRUE)) / (max(c, na.rm = TRUE) - min(c, na.rm = TRUE))
(d - min(d, na.rm = TRUE)) / (max(d, na.rm = TRUE) - min(d, na.rm = TRUE))
In jeder Zeile sind es genau vier Buchstaben (jeweils: a, b, c, d).
(█ - min(█, na.rm = TRUE)) / (max(█, na.rm = TRUE) - min(█, na.rm = TRUE))
Um daraus eine Funktion zu machen, bedarf es drei Dinge:
rescale01, weil die Funktion einen
Vektor in ein Intervall zwischen 0 und 1 packt.Ein Template sieht dann wie folgt aus:
name <- function(arguments) {
body
}
Er führt dann zu:
rescale01 <- function(x) {
(x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
}
rescale01(c(-10, 0, 10))
## [1] 0.0 0.5 1.0
rescale01(c(1, 2, 3, NA, 5))
## [1] 0.00 0.25 0.50 NA 1.00
Mithilfe von mutate():
df |> mutate(
a = rescale01(a),
b = rescale01(b),
c = rescale01(c),
d = rescale01(d),
)
## # A tibble: 5 × 4
## a b c d
## <dbl> <dbl> <dbl> <dbl>
## 1 0.798 0.691 0.643 0.0773
## 2 0.703 0.315 1 0.153
## 3 0.612 0.567 0.279 0
## 4 0 0 0.948 1
## 5 1 1 0 0.523
Mithilfe von range() können wir schnell das Minimum und
das Maximum berechnen.
rescale01 <- function(x) {
rng <- range(x, na.rm = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
}
Liegt ein unendlicher Wert vor, so haben wir ein Problem.
x <- c(1:10, Inf)
rescale01(x)
## [1] 0 0 0 0 0 0 0 0 0 0 NaN
Wir sagen also range() unendliche Werte bitte zu
ignorieren.
rescale01 <- function(x) {
rng <- range(x, na.rm = TRUE, finite = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
}
rescale01(x)
## [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667
## [8] 0.7777778 0.8888889 1.0000000 Inf
Wir wollen den z-Score berechnen. mutate()
bietet sich hier an, da sie dieselbe Länge (wie der Input) ausgeben.
z_score <- function(x) {
(x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
}
z_score(c(2,3,3,4))
## [1] -1.224745 0.000000 0.000000 1.224745
Mit case_when() können wir Werte ausgeben, die sich
innerhalb eines Intervalls befinden.
# .default = x funktioniert nicht, auch nicht mit 1:10
clamp <- function(x, min, max) {
case_when(
x < min ~ min,
x > max ~ max,
.default = x
)
}
clamp(1:10, min = 3, max = 7)
## [1] 3 3 3 4 5 6 7 7 7 7
clamp <- function(x, min, max) {
case_when(
x < min ~ 3,
x > max ~ 7,
TRUE ~ x
)
}
clamp(seq(1, 10, 1), min = 3, max = 7)
## [1] 3 3 3 4 5 6 7 7 7 7
Willst du Dollarzeichen, Prozentzeichen, Komma von einem String entfernen?
clean_number <- function(x) {
is_pct <- str_detect(x, "%")
num <- x |>
str_remove_all("[%]") |>
str_remove_all(",") |>
str_remove_all("[$]") |>
as.numeric(x)
if_else(is_pct, num / 100, num)
}
clean_number("$12,300")
## [1] 12300
clean_number("45%")
## [1] 0.45
Ersetze einen Vektor durch NA, wenn bestimmte Zahlen
vorkommen.
fix_na <- function(x) {
ifelse(x %in% c(997, 998, 999), NA, x)
}
fix_na(seq(990,1001,1))
## [1] 990 991 992 993 994 995 996 NA NA NA 1000 1001
Unsere Funktion kann aber natürlich auch mehrere Vektoren als Argumente aufnehmen.
haversine <- function(long1, lat1, long2, lat2, round = 3) {
# convert to radians
long1 <- long1 * pi / 180
lat1 <- lat1 * pi / 180
long2 <- long2 * pi / 180
lat2 <- lat2 * pi / 180
R <- 6371 # Earth mean radius in km
a <- sin((lat2 - lat1) / 2)^2 +
cos(lat1) * cos(lat2) * sin((long2 - long1) / 2)^2
d <- R * 2 * asin(sqrt(a))
round(d, round)
}
Summary Functions geben einen einzelnen Wert aus
(summarize()).
commas <- function(x) {
str_flatten(x, collapse = ", ", last = " and ")
}
commas(c("cat", "dog", "pigeon"))
## [1] "cat, dog and pigeon"
Berechne den Variationskoeffizienten:
cv <- function(x, na.rm = FALSE) {
sd(x, na.rm = na.rm) / mean(x, na.rm = na.rm)
}
cv(runif(100, min = 0, max = 50))
## [1] 0.6416063
cv(runif(100, min = 0, max = 500))
## [1] 0.5875608
Wieviele Missing Values? Verpasse immer einen Namen, an den du dich erinnern kannst.
n_missing <- function(x) {
sum(is.na(x))
}
n_missing(c(2,3,4,NA,NA,5))
## [1] 2
Vergleiche zwei Vektoren miteinander.
mape <- function(actual, predicted) {
sum(abs((actual - predicted) / actual)) / length(actual)
}
mape(c(1,2,3,2,3,4,2), c(2,3,2,3,4,4,2))
## [1] 0.3809524
Vektorfunktionen sind nützlich, um Code herauszuziehen, der innerhalb eines dplyr-Verbs wiederholt wird. Aber oft wiederholen Sie auch die Verben selbst, insbesondere in einer großen Pipe. Wenn Sie feststellen, dass Sie mehrere Verben mehrmals kopieren und einfügen, sollten Sie über das Schreiben einer Data Frame Funktion nachdenken. Sie funktionieren ähnlich wie dplyr-Verben: Sie nehmen einen Data Frame als erstes Argument, einige zusätzliche Argumente, die angeben, was damit gemacht werden soll, und geben einen Data Frame oder Vektor zurück.
Bei Verwendung von dolyr Verben und Funktionen, stößt man schnell auf Probleme.
grouped_mean <- function(df, group_var, mean_var) {
df |>
group_by(group_var) |>
summarize(mean(mean_var))
}
diamonds |> grouped_mean(cut, carat)
#> Error in `group_by()`:
#> ! Must group by variables found in `.data`.
#> ✖ Column `group_var` is not found.
Machen wir es ein wenig deutlicher.
df <- tibble(
mean_var = 1,
group_var = "g",
group = 1,
x = 10,
y = 100
)
df |> grouped_mean(group, x)
## # A tibble: 1 × 2
## group_var `mean(mean_var)`
## <chr> <dbl>
## 1 g 1
#> # A tibble: 1 × 2
#> group_var `mean(mean_var)`
#> <chr> <dbl>
#> 1 g 1
df |> grouped_mean(group, y)
## # A tibble: 1 × 2
## group_var `mean(mean_var)`
## <chr> <dbl>
## 1 g 1
#> # A tibble: 1 × 2
#> group_var `mean(mean_var)`
#> <chr> <dbl>
#> 1 g 1
Egal wie wir grouped_mean() nennen, es macht
|> group_by(group_var)
|> summarize(mean(mean_var)), statt
df |> group_by(group)
|> summarize(mean(x)). Embracing
bedeutet, dass die Variable in geschweifte Klammern gepackt wird.
grouped_mean <- function(df, group_var, mean_var) {
df |>
group_by({{ group_var }}) |>
summarize(mean({{ mean_var }}))
}
df |> grouped_mean(group, x)
## # A tibble: 1 × 2
## group `mean(x)`
## <dbl> <dbl>
## 1 1 10
Die Lösung findest du in der Dokumentation. *
Data-masking: arrange(),
filter(), summarize(). *
Tidy-selection: select(),
relocate(), rename().
summary6 <- function(data, var) {
data |> summarize(
min = min({{ var }}, na.rm = TRUE),
mean = mean({{ var }}, na.rm = TRUE),
median = median({{ var }}, na.rm = TRUE),
max = max({{ var }}, na.rm = TRUE),
n = n(),
n_miss = sum(is.na({{ var }})),
.groups = "drop"
)
}
diamonds |> summary6(carat)
## # A tibble: 1 × 6
## min mean median max n n_miss
## <dbl> <dbl> <dbl> <dbl> <int> <int>
## 1 0.2 0.798 0.7 5.01 53940 0
Das Schöne an dieser Funktion ist, dass wir sie auf gruppierten Daten
verwenden können, da sie summarize() umschließt.
diamonds |>
group_by(cut) |>
summary6(carat)
## # A tibble: 5 × 7
## cut min mean median max n n_miss
## <ord> <dbl> <dbl> <dbl> <dbl> <int> <int>
## 1 Fair 0.22 1.05 1 5.01 1610 0
## 2 Good 0.23 0.849 0.82 3.01 4906 0
## 3 Very Good 0.2 0.806 0.71 4 12082 0
## 4 Premium 0.2 0.892 0.86 4.01 13791 0
## 5 Ideal 0.2 0.703 0.54 3.5 21551 0
Berechnete Variablen können wir so auch mit summarize
benutzen.
diamonds |>
group_by(cut) |>
summary6(log10(carat))
## # A tibble: 5 × 7
## cut min mean median max n n_miss
## <ord> <dbl> <dbl> <dbl> <dbl> <int> <int>
## 1 Fair -0.658 -0.0273 0 0.700 1610 0
## 2 Good -0.638 -0.133 -0.0862 0.479 4906 0
## 3 Very Good -0.699 -0.164 -0.149 0.602 12082 0
## 4 Premium -0.699 -0.125 -0.0655 0.603 13791 0
## 5 Ideal -0.699 -0.225 -0.268 0.544 21551 0
Auch count() ist nützlich und berechnet Anteile.
count_prop <- function(df, var, sort = FALSE) {
df |>
count({{ var }}, sort = sort) |>
mutate(prop = n / sum(n))
}
diamonds |> count_prop(clarity)
## # A tibble: 8 × 3
## clarity n prop
## <ord> <int> <dbl>
## 1 I1 741 0.0137
## 2 SI2 9194 0.170
## 3 SI1 13065 0.242
## 4 VS2 12258 0.227
## 5 VS1 8171 0.151
## 6 VVS2 5066 0.0939
## 7 VVS1 3655 0.0678
## 8 IF 1790 0.0332
Nur das zweite Argument der drei: df, var,
sort muss umklammert werden, da count()
data-masking für alle Variablen benutzt. sort hat
als default (Wert) FALSE.
unique_where <- function(df, condition, var) {
df |>
filter({{ condition }}) |>
distinct({{ var }}) |>
arrange({{ var }})
}
# Find all the destinations in December
flights |> unique_where(month == 12, dest)
## # A tibble: 96 × 1
## dest
## <chr>
## 1 ABQ
## 2 ALB
## 3 ATL
## 4 AUS
## 5 AVL
## 6 BDL
## 7 BGR
## 8 BHM
## 9 BNA
## 10 BOS
## # ℹ 86 more rows
Wenn du immer mit demselben Datensatz arbeitest, kann es Sinn machen den Datensatz fest einzuprogrammieren. Als Spalte kann eine Zahl entsprechend der Spaltenzahl dienen, oder der Name mit, oder ohne Anführungszeichen.
subset_flights <- function(rows, cols) {
flights |>
filter({{ rows }}) |>
select(time_hour, carrier, flight, {{ cols }})
}
subset_flights(TRUE, "year")
## # A tibble: 336,776 × 4
## time_hour carrier flight year
## <dttm> <chr> <int> <int>
## 1 2013-01-01 05:00:00 UA 1545 2013
## 2 2013-01-01 05:00:00 UA 1714 2013
## 3 2013-01-01 05:00:00 AA 1141 2013
## 4 2013-01-01 05:00:00 B6 725 2013
## 5 2013-01-01 06:00:00 DL 461 2013
## 6 2013-01-01 05:00:00 UA 1696 2013
## 7 2013-01-01 06:00:00 B6 507 2013
## 8 2013-01-01 06:00:00 EV 5708 2013
## 9 2013-01-01 06:00:00 B6 79 2013
## 10 2013-01-01 06:00:00 AA 301 2013
## # ℹ 336,766 more rows
subset_flights(nrow(flights) <= 10, 1)
## # A tibble: 0 × 4
## # ℹ 4 variables: time_hour <dttm>, carrier <chr>, flight <int>, year <int>
Manchmal möchtest du Variablen innerhalb einer Funktion auswählen, die Datenmaskierung verwendet
count_missing <- function(df, group_vars, x_var) {
df |>
group_by({{ group_vars }}) |>
summarize(
n_miss = sum(is.na({{ x_var }})),
.groups = "drop"
)
}
flights |>
count_missing(c(year, month, day), dep_time)
#> Error in `group_by()`:
#> ℹ In argument: `c(year, month, day)`.
#> Caused by error:
#> ! `c(year, month, day)` must be size 336776 or 1, not 1010328.
Tidy-selection in einer data-masking Funktion lässt
sich benutzen mit pick(). Der Code oben funktioniert nicht,
da group_by() data-masking, nicht
tidy-selection benutzt.
count_missing <- function(df, group_vars, x_var) {
df |>
group_by(pick({{ group_vars }})) |>
summarize(
n_miss = sum(is.na({{ x_var }})),
.groups = "drop"
)
}
flights |>
count_missing(c(year, month, day), dep_time)
## # A tibble: 365 × 4
## year month day n_miss
## <int> <int> <int> <int>
## 1 2013 1 1 4
## 2 2013 1 2 8
## 3 2013 1 3 10
## 4 2013 1 4 6
## 5 2013 1 5 3
## 6 2013 1 6 1
## 7 2013 1 7 3
## 8 2013 1 8 4
## 9 2013 1 9 5
## 10 2013 1 10 3
## # ℹ 355 more rows
count_wide <- function(data, rows, cols) {
data |>
count(pick(c({{ rows }}, {{ cols }}))) |>
pivot_wider(
names_from = {{ cols }},
values_from = n,
names_sort = TRUE,
values_fill = 0
)
}
diamonds |> count_wide(c(clarity, color), cut)
## # A tibble: 56 × 7
## clarity color Fair Good `Very Good` Premium Ideal
## <ord> <ord> <int> <int> <int> <int> <int>
## 1 I1 D 4 8 5 12 13
## 2 I1 E 9 23 22 30 18
## 3 I1 F 35 19 13 34 42
## 4 I1 G 53 19 16 46 16
## 5 I1 H 52 14 12 46 38
## 6 I1 I 34 9 8 24 17
## 7 I1 J 23 4 8 13 2
## 8 SI2 D 56 223 314 421 356
## 9 SI2 E 78 202 445 519 469
## 10 SI2 F 89 201 343 523 453
## # ℹ 46 more rows
Anstatt eines Data Frames wollen wir einen Plot ausgeben
lassen. Du kannst dieselbe Technik mit ggplot2 verwenden,
da aes() eine data-masking Funktion ist.
diamonds |>
ggplot(aes(x = carat)) +
geom_histogram(binwidth = 0.1)
diamonds |>
ggplot(aes(x = carat)) +
geom_histogram(binwidth = 0.05)
Es wäre aber doch viel schöner, wenn du diesen Code in eine Histogramm Funktion packen könntest.
histogram <- function(df, var, binwidth = NULL) {
df |>
ggplot(aes(x = {{ var }})) +
geom_histogram(binwidth = binwidth)
}
diamonds |> histogram(carat, 0.1)
Du kannst natürlich noch durch + weitere Komponenten
hinzufügen.
diamonds |>
histogram(carat, 0.1) +
labs(x = "Size (in carats)", y = "Number of diamonds")
Mehr Variablen können natürlich hinzugefügt werden.
linearity_check <- function(df, x, y) {
df |>
ggplot(aes(x = {{ x }}, y = {{ y }})) +
geom_point() +
geom_smooth(method = "loess", formula = y ~ x, color = "red", se = FALSE) +
geom_smooth(method = "lm", formula = y ~ x, color = "blue", se = FALSE)
}
starwars |>
filter(mass < 1000) |>
linearity_check(mass, height)
hex_plot <- function(df, x, y, z, bins = 20, fun = "mean") {
df |>
ggplot(aes(x = {{ x }}, y = {{ y }}, z = {{ z }})) +
stat_summary_hex(
aes(color = after_scale(fill)), # make border same color as fill
bins = bins,
fun = fun,
)
}
diamonds |> hex_plot(carat, price, depth)
tidyverseWir wollen ein vertikales Säulendiagramm erstellen, bei dem die Reihenfolge fallend, statt aufsteigend ist.
sorted_bars <- function(df, var) {
df |>
mutate({{ var }} := fct_rev(fct_infreq({{ var }}))) |>
ggplot(aes(y = {{ var }})) +
geom_bar()
}
diamonds |> sorted_bars(clarity)
Hier haben wir eine neuen Operator, :=. R erlaubt hier
nur einen einfachen Wortnamen, wir wollen aber unsere Variable
überschreiben. Von tidy wird er wie ein =
bewertet.
Einen Plot für eine Teilmenge mithilfe von filter() in
einer Funktion können wir auch leicht erstellen.
conditional_bars <- function(df, condition, var) {
df |>
filter({{ condition }}) |>
ggplot(aes(x = {{ var }})) +
geom_bar()
}
diamonds |> conditional_bars(cut == "Good", clarity)
histogram <- function(df, var, binwidth = NULL) {
df |>
ggplot(aes(x = {{ var }})) +
geom_histogram(binwidth = binwidth)
}
Warum nicht hier eine Überschrift hinzufügen? Dazu benutzen wir das
rlang Paket. Dazu benutzen wir
rlang::englue(). Jeder Wert in {} wird in den
String eingeführt. In {{}} wird der Variablenname
eingesetzt.
histogram <- function(df, var, binwidth) {
label <- rlang::englue("A histogram of {{var}} with binwidth {binwidth}")
df |>
ggplot(aes(x = {{ var }})) +
geom_histogram(binwidth = binwidth) +
labs(title = label)
}
diamonds |> histogram(carat, 0.1)
R ist es egal wie du deine Funktionen oder Namen benennst. Kurz sollten sie sein, aber auch sollte man eine Idee bekommen wie sich die Funktion verhält. Funktionsnamen sind meist verben und Argumente Nomen.
# Too short
f()
# Not a verb, or descriptive
my_awesome_function()
# Long, but clear
impute_missing()
collapse_years()
Einrücken der Zeilen nicht vergessen.
# Missing extra two spaces
density <- function(color, facets, binwidth = 0.1) {
diamonds |>
ggplot(aes(x = carat, y = after_stat(density), color = {{ color }})) +
geom_freqpoly(binwidth = binwidth) +
facet_wrap(vars({{ facets }}))
}
# Pipe indented incorrectly
density <- function(color, facets, binwidth = 0.1) {
diamonds |>
ggplot(aes(x = carat, y = after_stat(density), color = {{ color }})) +
geom_freqpoly(binwidth = binwidth) +
facet_wrap(vars({{ facets }}))
}
Willst du einen numerischen Vektor verdoppeln, so reicht es einfach
2 * x zu schreiben. Ähnliche Werkzeuge für Wiederholungen
haben wir schon kennengelernt:
facet_wrap() und facet_grid zeichnet einen
Plot für jede Teilmenge.group_by mit summarize() berechnet
zusammenfassende Statistiken für Untergruppen.unnest_wider() und unnest_longer()
erstellen neue Zeilen und Spalten.Jetzt lernen wir generelle Werkzeuge, functional programming Werkzeuge kennen. Sie werden so genannt, da sie um Funktionen gebaut werden, die andere Funktionen als Input nehmen.
library(tidyverse)
Wir brauchen das bekannte dplyr und das neue purrr. Ein hervorrangendes Paket.
Wir schauen uns ein simplen tibble an und wollen Anzahl
der Beobachtungen, sowie Median, jeder Spalte berechen.
df <- tibble(
a = rnorm(10),
b = rnorm(10),
c = rnorm(10),
d = rnorm(10)
)
df
## # A tibble: 10 × 4
## a b c d
## <dbl> <dbl> <dbl> <dbl>
## 1 -2.59 0.335 0.541 -0.424
## 2 0.201 -0.337 -0.523 0.122
## 3 -0.0302 -0.289 -1.39 0.540
## 4 0.585 -0.0502 0.370 1.23
## 5 -0.499 -0.696 -0.0552 -0.455
## 6 1.30 0.261 -1.26 1.62
## 7 -1.76 0.344 -0.317 -0.641
## 8 2.29 -2.62 2.21 -2.93
## 9 1.26 0.657 0.724 0.201
## 10 0.580 -0.427 0.620 -0.864
df |> summarize(
n = n(),
a = median(a),
b = median(b),
c = median(c),
d = median(d),
)
## # A tibble: 1 × 5
## n a b c d
## <int> <dbl> <dbl> <dbl> <dbl>
## 1 10 0.390 -0.170 0.158 -0.151
Wir können es mit copy-paste erledigen. Das kann sehr mühsam
sein, Oder wir benutzen across():
df |> summarize(
n = n(),
across(a:d, median),
)
## # A tibble: 1 × 5
## n a b c d
## <int> <dbl> <dbl> <dbl> <dbl>
## 1 10 0.390 -0.170 0.158 -0.151
Es hat drei wichtige Argumente, wobei die ersten beiden elementar
sind: .cols bestimmt die Spalten über die iteriert werden
soll und .fns was mit jeder Spalte gemacht werden soll. Das
.names Argument benutzt du, wenn du zusätzlich Kontrolle
über über die Namen des Outputs gewinnen willst.
.colsDas erste Argument sucht die zu transformierenden Spalten aus. Es
benutzt dieselbe Spezifikation wie select(), sodass du auch
starts_with() und ends_with() benutzen
kannst.
dfs <- tibble( w1 = rnorm(4), w2 = rnorm(4) + 5, s2 = rnorm(4) * 100)
dfs
## # A tibble: 4 × 3
## w1 w2 s2
## <dbl> <dbl> <dbl>
## 1 0.781 7.39 -60.2
## 2 -0.887 5.69 -210.
## 3 1.57 6.17 83.7
## 4 1.65 3.32 201.
dfs|>
select(starts_with("w"))
## # A tibble: 4 × 2
## w1 w2
## <dbl> <dbl>
## 1 0.781 7.39
## 2 -0.887 5.69
## 3 1.57 6.17
## 4 1.65 3.32
dfs|>
summarize(
n = n(),
across(c(w1, w2, s2), median)
)
## # A tibble: 1 × 4
## n w1 w2 s2
## <int> <dbl> <dbl> <dbl>
## 1 4 1.18 5.93 11.7
Zwei weitere Auswahltechniken sind sehr nützlich für
across(): everything und where().
everything ist straightforward: es wählt jede
nicht-gruppierte Spalte.
df <- tibble(
grp = sample(2, 10, replace = TRUE),
a = rnorm(10),
b = rnorm(10),
c = rnorm(10),
d = rnorm(10)
)
df |>
group_by(grp) |>
summarize(across(everything(), median))
## # A tibble: 2 × 5
## grp a b c d
## <int> <dbl> <dbl> <dbl> <dbl>
## 1 1 -0.241 0.194 -0.253 -0.0615
## 2 2 0.760 0.919 0.00556 0.197
where() erlaubt es Spalten aufgrund ihres Types
auszuwählen: - where(is.numeric) -
where(is.Date) - …
Du kannst die Selektoren miteinander kombinieren:
starts_with("a") & where(is.logical). Hier werden alle
logischen Vektoren ausgewählt, deren Namen mit “a” starten.
Wir übergeben eine Funktion an eine andere. Wir übergeben diese
Funktion an across(). Wir rufen sie nicht selbst auf. Dem
Funktionsnamen folgt kein (). Sonst gibt es eine
Fehlermeldung. Besser also ohne.
k = function(x) mean(x)
df|>
group_by(grp)|>
summarize(across(everything(), k))
## # A tibble: 2 × 5
## grp a b c d
## <int> <dbl> <dbl> <dbl> <dbl>
## 1 1 -0.552 0.168 -0.608 -0.221
## 2 2 0.692 0.531 -0.0817 0.135
Haben wir Missing Values in unserem Datensatz, so wollen wir diese natürlich entfernen.
rnorm_na <- function(n, n_na, mean = 0, sd = 1) {
sample(c(rnorm(n - n_na, mean = mean, sd = sd), rep(NA, n_na)))
}
df_miss <- tibble(
a = rnorm_na(5, 1),
b = rnorm_na(5, 1),
c = rnorm_na(5, 2),
d = rnorm(5)
)
df_miss |>
summarize(
across(a:d, median),
n = n()
)
## # A tibble: 1 × 5
## a b c d n
## <dbl> <dbl> <dbl> <dbl> <int>
## 1 NA NA NA 0.0920 5
Das können wir natürlich leicht mithilfe von na.rm = T.
Statt den Median durch median() aufzurufen, müssen wir eine
neue Funktion kreieren.
df_miss |>
summarize(
across(a:d, function(x) median(x, na.rm = TRUE)),
n = n()
)
## # A tibble: 1 × 5
## a b c d n
## <dbl> <dbl> <dbl> <dbl> <int>
## 1 0.328 -0.0381 0.136 0.0920 5
Es geht aber noch ein wenig kürzer, indem man function
durch \ ersetzt.
df_miss |>
summarize(
across(a:d, \(x) median(x, na.rm = TRUE)),
n = n()
)
## # A tibble: 1 × 5
## a b c d n
## <dbl> <dbl> <dbl> <dbl> <int>
## 1 0.328 -0.0381 0.136 0.0920 5
Was geht wohl schneller?
df_miss |>
summarize(
a = median(a, na.rm = TRUE),
b = median(b, na.rm = TRUE),
c = median(c, na.rm = TRUE),
d = median(d, na.rm = TRUE),
n = n()
)
## # A tibble: 1 × 5
## a b c d n
## <dbl> <dbl> <dbl> <dbl> <int>
## 1 0.328 -0.0381 0.136 0.0920 5
Wir können jetzt sogar noch eine weitere Funktion hinzufügen. In eine Liste.
df_miss |>
summarize(
across(a:d, list(
median = \(x) median(x, na.rm = TRUE),
n_miss = \(x) sum(is.na(x))
)),
n = n()
)
## # A tibble: 1 × 9
## a_median a_n_miss b_median b_n_miss c_median c_n_miss d_median d_n_miss n
## <dbl> <int> <dbl> <int> <dbl> <int> <dbl> <int> <int>
## 1 0.328 1 -0.0381 1 0.136 2 0.0920 0 5
Achte auf die neuen Spaltennamen. Das ist kein Zufall:
{.col}_{.fn}. Der Name ist eine Kombination aus Spaltenname
und Funktion.
Die können wir jetzt selber festlegen, wenn wir z.B. zuerst den Namen der Funktion uns wünschen.
df_miss |>
summarize(
across(
a:d,
list(
median = \(x) median(x, na.rm = TRUE),
n_miss = \(x) sum(is.na(x))
),
.names = "{.fn}_{.col}"
),
n = n(),
)
## # A tibble: 1 × 9
## median_a n_miss_a median_b n_miss_b median_c n_miss_c median_d n_miss_d n
## <dbl> <int> <dbl> <int> <dbl> <int> <dbl> <int> <int>
## 1 0.328 1 -0.0381 1 0.136 2 0.0920 0 5
Das .names Argument ist besonders wichtig, wenn du
across() zusammen mit mutate() benutzt.
across() innerhalb von mutate() ersetzt
existierende Spalten. In unserem Fall ersetzt coalesce()
NA durch 0.
df_miss |>
mutate(
across(a:d, \(x) coalesce(x, 0))
)
## # A tibble: 5 × 4
## a b c d
## <dbl> <dbl> <dbl> <dbl>
## 1 0.323 0 0 0.550
## 2 -0.525 -0.259 0.136 0.0920
## 3 0.334 0.291 -0.736 -0.439
## 4 0 -0.146 0 0.298
## 5 0.440 0.0696 0.660 -1.28
Du kannst aber auch neue Spalten kreieren, indem du durch
.names dem Output neue Namen verpasst.
df_miss |>
mutate(
across(a:d, \(x) abs(x), .names = "{.col}_abs")
)
## # A tibble: 5 × 8
## a b c d a_abs b_abs c_abs d_abs
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.323 NA NA 0.550 0.323 NA NA 0.550
## 2 -0.525 -0.259 0.136 0.0920 0.525 0.259 0.136 0.0920
## 3 0.334 0.291 -0.736 -0.439 0.334 0.291 0.736 0.439
## 4 NA -0.146 NA 0.298 NA 0.146 NA 0.298
## 5 0.440 0.0696 0.660 -1.28 0.440 0.0696 0.660 1.28
across() funktioniert sehr gut mit
summarize() und mutate(), aber nicht wirklich
mit filter(). dplyr bietet zwei Varianten
von across()an: if_any() und
if_all().
# same as df_miss |> filter(is.na(a) | is.na(b) | is.na(c) | is.na(d))
df_miss |> filter(if_any(a:d, is.na))
## # A tibble: 2 × 4
## a b c d
## <dbl> <dbl> <dbl> <dbl>
## 1 0.323 NA NA 0.550
## 2 NA -0.146 NA 0.298
Jede Zeile wird übernommen, in der mindestens ein NA
Wert ist. Oder in der nur NA Werte sind.
# same as df_miss |> filter(is.na(a) & is.na(b) & is.na(c) & is.na(d))
df_miss |> filter(if_all(a:d, is.na))
## # A tibble: 0 × 4
## # ℹ 4 variables: a <dbl>, b <dbl>, c <dbl>, d <dbl>
across() in Funktionenacross() ist sehr nützlich, da es einem erlaubt auch auf
multiplen Spalten zu operieren.
expand_dates <- function(df) {
df |>
mutate(
across(where(is.Date), list(year = year, month = month, day = mday))
)
}
df_date <- tibble(
name = c("Amy", "Bob"),
date = ymd(c("2009-08-03", "2010-01-16"))
)
df_date |>
expand_dates()
## # A tibble: 2 × 5
## name date date_year date_month date_day
## <chr> <date> <dbl> <dbl> <int>
## 1 Amy 2009-08-03 2009 8 3
## 2 Bob 2010-01-16 2010 1 16
Berechne das arithmetische Mittel von numerischen Spalten.
summarize_means <- function(df, summary_vars = where(is.numeric)) {
df |>
summarize(
across({{ summary_vars }}, \(x) mean(x, na.rm = TRUE)),
n = n()
)
}
diamonds |>
group_by(cut) |>
summarize_means()
## # A tibble: 5 × 11
## cut carat depth table price x y z log_price log_carat n
## <ord> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int>
## 1 Fair 1.05 64.0 59.1 4359. 6.25 6.18 3.98 8.09 -0.0629 1610
## 2 Good 0.849 62.4 58.7 3929. 5.84 5.85 3.64 7.84 -0.307 4906
## 3 Very Good 0.806 61.8 58.0 3982. 5.74 5.77 3.56 7.80 -0.379 12082
## 4 Premium 0.892 61.3 58.7 4584. 5.97 5.94 3.65 7.95 -0.288 13791
## 5 Ideal 0.703 61.7 56.0 3458. 5.51 5.52 3.40 7.64 -0.518 21551
diamonds |>
group_by(cut) |>
summarize_means(c(carat, x:z))
## # A tibble: 5 × 6
## cut carat x y z n
## <ord> <dbl> <dbl> <dbl> <dbl> <int>
## 1 Fair 1.05 6.25 6.18 3.98 1610
## 2 Good 0.849 5.84 5.85 3.64 4906
## 3 Very Good 0.806 5.74 5.77 3.56 12082
## 4 Premium 0.892 5.97 5.94 3.65 13791
## 5 Ideal 0.703 5.51 5.52 3.40 21551
pivot_longer()Mithilfe von pivot_longer konnten wir aus einer
Kreuztabelle, eine lange Tabelle machen. Oftmals wird erst
pivot_longer() auf die Tabelle angesetzt, dann folgt
group_by().
df |>
summarize(across(a:d, list(median = median, mean = mean)))
## # A tibble: 1 × 8
## a_median a_mean b_median b_mean c_median c_mean d_median d_mean
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.188 -0.0543 0.610 0.313 -0.106 -0.397 0.00790 -0.0785
Dieselben Werte erhalten wir natürlich, indem wir
pivot_longer() benutzen und dann
summarize().
long <- df |>
pivot_longer(a:d) |>
group_by(name) |>
summarize(
median = median(value),
mean = mean(value)
)
Benutze pivot_wider() um wieder zur alten Struktur
zurückzukehren.
long |>
pivot_wider(
names_from = name,
values_from = c(median, mean),
names_vary = "slowest",
names_glue = "{name}_{.value}"
)
## # A tibble: 1 × 8
## a_median a_mean b_median b_mean c_median c_mean d_median d_mean
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.188 -0.0543 0.610 0.313 -0.106 -0.397 0.00790 -0.0785
Es ist eine nützliche Technik, auch wenn die Namen der Variablen “schwierig” sind.
df_paired <- tibble(
a_val = rnorm(10),
a_wts = runif(10),
b_val = rnorm(10),
b_wts = runif(10),
c_val = rnorm(10),
c_wts = runif(10),
d_val = rnorm(10),
d_wts = runif(10)
)
Keine Chance mit across(), aber mit
pivot_longer().
df_long <- df_paired |>
pivot_longer(
everything(),
names_to = c("group", ".value"),
names_sep = "_"
)
df_long
## # A tibble: 40 × 3
## group val wts
## <chr> <dbl> <dbl>
## 1 a -0.428 0.678
## 2 b -0.512 0.661
## 3 c 1.21 0.406
## 4 d -0.260 0.205
## 5 a -0.188 0.628
## 6 b -0.760 0.0532
## 7 c 0.156 0.0774
## 8 d 0.396 0.973
## 9 a 1.86 0.673
## 10 b 1.88 0.0996
## # ℹ 30 more rows
df_long |>
group_by(group) |>
summarize(mean = weighted.mean(val, wts))
## # A tibble: 4 × 2
## group mean
## <chr> <dbl>
## 1 a 0.221
## 2 b 0.325
## 3 c 0.190
## 4 d 0.332
Hier geht es darum purrr::map() zu benutzen, dass
Transformationen nicht in multiplen Spalten vornimmt, sondern in jedem
Ordner deiner directory. Da das Thema sehr speziell ist,
schieben wir es mal nach hinten. Genauso wie das nächste.
Speziell, da geschoben.
Wir haben viel tidyverse benutzt, aber es geht auch ohne Pakete. Trotzdem laden wir unser Standardpaket.
library(tidyverse)
[Eckige Klammern werden benutzt, um Teilmengen aus Vektoren oder
Data Frames zu gewinnen. Viele dplyr Verben
sind Spizialfälle von [.
Es gibt 5 Möglichkeiten einen Vektor zu unterteilen.
x[i] wäre ein Beispiel:
slice(1:2) z.B. Zeilen auswählen.x <- c("one", "two", "three", "four", "five")
x[c(3, 2, 5)]
## [1] "three" "two" "five"
x[2]
## [1] "two"
x[c(1, 1, 5, 5, 5, 2)]
## [1] "one" "one" "five" "five" "five" "two"
-. Bitte nicht + und
- mischen:x[c(-1, -3, -5)]
## [1] "two" "four"
TRUE korrespondieren. Bei
Vergleichen wird es oft gebraucht.x <- c(10, 3, NA, 5, 8, 1, NA)
# All non-missing values of x
x[!is.na(x)]
## [1] 10 3 5 8 1
# All even (or missing!) values of x
x[x %% 2 == 0]
## [1] 10 NA 8 NA
x <- c(abc = 1, def = 2, xyz = 5)
x[c("xyz", "def")]
## xyz def
## 5 2
df[rows, cols] wählt den Wert eines Data Frames
aus. Lasse ich eine Seite weg, so werden ALLE Werte der Zeile oder
Spalte weggelassen.
df <- tibble(
x = 1:3,
y = c("a", "e", "f"),
z = runif(3)
)
df
## # A tibble: 3 × 3
## x y z
## <int> <chr> <dbl>
## 1 1 a 0.104
## 2 2 e 0.402
## 3 3 f 0.770
# Select first row and second column
df[1, 2]
## # A tibble: 1 × 1
## y
## <chr>
## 1 a
# Select all rows and columns x and y
df[, c("x" , "y")]
## # A tibble: 3 × 2
## x y
## <int> <chr>
## 1 1 a
## 2 2 e
## 3 3 f
# Select rows where `x` is greater than 1 and all columns
df[df$x > 1, ]
## # A tibble: 2 × 3
## x y z
## <int> <chr> <dbl>
## 1 2 e 0.402
## 2 3 f 0.770
$ bei df$x wählt die Variable
x von df aus. Es gibt einen Unterschied
zwischen tibbles und Data Frames bzgl [.
Bei einem Data Frame wird ein Vektor erzeugt, wenn nur eine
Variable ausgewählt wird. Bei einem tibble wird immer wieder
ein tibble erzeugt.
df1 <- data.frame(x = 1:3)
df1[, "x"]
## [1] 1 2 3
df2 <- tibble(x = 1:3)
df2[, "x"]
## # A tibble: 3 × 1
## x
## <int>
## 1 1
## 2 2
## 3 3
Verhindere es mit:
df1[, "x" , drop = FALSE]
## x
## 1 1
## 2 2
## 3 3
filter() ist äquivalent zu Subsetting die
Reihen mithilfe eines logischen Vektors.df <- tibble(
x = c(2, 3, 1, 1, NA),
y = letters[1:5],
z = runif(5)
)
df |> filter(x > 1)
## # A tibble: 2 × 3
## x y z
## <dbl> <chr> <dbl>
## 1 2 a 0.347
## 2 3 b 0.523
# same as
df[!is.na(df$x) & df$x > 1, ]
## # A tibble: 2 × 3
## x y z
## <dbl> <chr> <dbl>
## 1 2 a 0.347
## 2 3 b 0.523
which(df$x > 1)
## [1] 1 2
df[which(df$x > 1), ]
## # A tibble: 2 × 3
## x y z
## <dbl> <chr> <dbl>
## 1 2 a 0.347
## 2 3 b 0.523
arrange() zu order():df |> arrange(x, y)
## # A tibble: 5 × 3
## x y z
## <dbl> <chr> <dbl>
## 1 1 c 0.368
## 2 1 d 0.851
## 3 2 a 0.347
## 4 3 b 0.523
## 5 NA e 0.934
df |> arrange(x, y, decreasing = TRUE)
## # A tibble: 5 × 3
## x y z
## <dbl> <chr> <dbl>
## 1 1 c 0.368
## 2 1 d 0.851
## 3 2 a 0.347
## 4 3 b 0.523
## 5 NA e 0.934
# same as
df[order(df$x, df$y), ]
## # A tibble: 5 × 3
## x y z
## <dbl> <chr> <dbl>
## 1 1 c 0.368
## 2 1 d 0.851
## 3 2 a 0.347
## 4 3 b 0.523
## 5 NA e 0.934
df[order(df$x, df$y), decreasing = TRUE, ]
## # A tibble: 5 × 3
## x y z
## <dbl> <chr> <dbl>
## 1 1 c 0.368
## 2 1 d 0.851
## 3 2 a 0.347
## 4 3 b 0.523
## 5 NA e 0.934
Umgedrehte Reihenfolge mit order(decreasing = TRUE),
oder -rank(col)
select() und relocate() zu einem
Character Vector.df |> select(x, z)
## # A tibble: 5 × 2
## x z
## <dbl> <dbl>
## 1 2 0.347
## 2 3 0.523
## 3 1 0.368
## 4 1 0.851
## 5 NA 0.934
# same as
df[, c("x", "z")]
## # A tibble: 5 × 2
## x z
## <dbl> <dbl>
## 1 2 0.347
## 2 3 0.523
## 3 1 0.368
## 4 1 0.851
## 5 NA 0.934
In R Base kann man auch filter() und
select() kombinieren, durch subset().
df |>
filter(x > 1) |>
select(y, z)
## # A tibble: 2 × 2
## y z
## <chr> <dbl>
## 1 a 0.347
## 2 b 0.523
df |> subset(x > 1, c(y, z))
## # A tibble: 2 × 2
## y z
## <chr> <dbl>
## 1 a 0.347
## 2 b 0.523
$ und
[[Hier zeige ich dir wie du [[ und $ benutzt,
um aus Data Frames Spalten zu ziehen. Unterschiede zwischen
[ und [[ werden wir in Listen kennenlernen und
Unterschiede zwischen data.frames und tibbles.
[[ und $ können benutzt werden, um Spalten
aus einem Data Frame zu ziehen. Hier könen Position oder Name
bzw. der Name der Spalte stehen.
tb <- tibble(
x = 1:4,
y = c(10, 4, 1, 21)
)
# by position
tb[[1]]
## [1] 1 2 3 4
# by name
tb[["x"]]
## [1] 1 2 3 4
tb$x
## [1] 1 2 3 4
Wir können auch neue Spalten kreieren., so wie wir es durch
mutate() schon kennen.
tb$z <- tb$x + tb$y
tb
## # A tibble: 4 × 3
## x y z
## <int> <dbl> <dbl>
## 1 1 10 11
## 2 2 4 6
## 3 3 1 4
## 4 4 21 25
Weitere Beispiele mit transform(), with()
und within():
data(diamonds, package = "ggplot2")
# Most straightforward
diamonds$ppc <- diamonds$price / diamonds$carat
# Avoid repeating diamonds
diamonds$ppc <- with(diamonds, price / carat)
# The inspiration for dplyr's mutate
diamonds <- transform(diamonds, ppc = price / carat)
diamonds <- diamonds |> transform(ppc = price / carat)
# Similar to transform(), but uses assignment rather argument matching
# (can also use = here, since = is equivalent to <- outside of a function call)
diamonds <- within(diamonds, {
ppc <- price / carat
})
diamonds <- diamonds |> within({
ppc <- price / carat
})
# Protect against partial matching
diamonds$ppc <- diamonds[["price"]] / diamonds[["carat"]]
diamonds$ppc <- diamonds[, "price"] / diamonds[, "carat"]
$ direkt zu benutzen ist bequem, wenn man schnelle
Zusammenfassungen braucht. Dann gibt es keine Notwendigkeit
summarize() zu benutzen.
max(diamonds$carat)
## [1] 5.01
levels(diamonds$cut)
## [1] "Fair" "Good" "Very Good" "Premium" "Ideal"
dplyr hat auch ein Äquivalent zu [[/$
im Angebot: pull(). Es nimmt entweder einen Variablennmanen
oder die Position einer Variable und gibt gerade diese Spalte aus. So
können die Pipe benutzen:
diamonds |> pull(carat) |> max()
## [1] 5.01
diamonds |> pull(cut) |> levels()
## [1] "Fair" "Good" "Very Good" "Premium" "Ideal"
In Bezug auf $ unterscheiden sich tibbles und
base data.frame(). Im Gegensatz zu
tibble() wird bei sata.frame() keine
Fehlermeldung ausgegeben.
df <- data.frame(x1 = 1)
df$x
## [1] 1
df$z
## NULL
tb <- tibble(x1 = 1)
tb$x
## Warning: Unknown or uninitialised column: `x`.
## NULL
tb$z
## Warning: Unknown or uninitialised column: `z`.
## NULL
Der Unterschied bei Listen zu [ ist sehr wichtig zu
verstehen.
l <- list(
a = 1:3,
b = "a string",
c = pi,
d = list(-1, -5)
)
[ extrahiert eine Subliste.
str(l[1:2])
## List of 2
## $ a: int [1:3] 1 2 3
## $ b: chr "a string"
str(l[1])
## List of 1
## $ a: int [1:3] 1 2 3
str(l[4])
## List of 1
## $ d:List of 2
## ..$ : num -1
## ..$ : num -5
Bestimme die Subliste mit logical, integer oder character vector.
[[ und $ extrahieren eine einzige
Komponente von einer Liste.
str(l[[1]])
## int [1:3] 1 2 3
str(l[[4]])
## List of 2
## $ : num -1
## $ : num -5
str(l$a)
## int [1:3] 1 2 3
Das wichtigste Mitglied der Familie ist lapply(),
welches sehr ähnlich zu purrr::map() ist. Du kannst jeden
map() call durch lapply()
ersetzen.
Es gibt kein Äquivalent zu across(), aber in R
Base kommst du durch [ mit lapply()
nah dran. lapply() auf einem Data Frame wendet die
Funktion auf jeder Spalte an.
df <- tibble(a = 1, b = 2, c = "a", d = "b", e = 4)
# First find numeric columns
num_cols <- sapply(df, is.numeric)
num_cols
## a b c d e
## TRUE TRUE FALSE FALSE TRUE
# Then transform each column with lapply() then replace the original values
df[, num_cols] <- lapply(df[, num_cols, drop = FALSE], \(x) x * 2)
df
## # A tibble: 1 × 5
## a b c d e
## <dbl> <dbl> <chr> <chr> <dbl>
## 1 2 4 a b 8
sapply() steckt den Output in einen Vektor,
lapply() in eine Liste.
R bietet eine striktere Version von sapply() an:
vapply(). Es nimmt ein weiteres Argument auf, dass den Typ
spezifiziert.
vapply(df, is.numeric, logical(1))
## a b c d e
## TRUE TRUE FALSE FALSE TRUE
vapply(df, is.numeric, numeric(1))
## a b c d e
## 1 1 0 0 1
Die Ausgabe hat einen logischen Wert bzw. einen numerischen.
tapply() berechnet eine gruppierte Zusammenfassung durch
eine Funktion wie mean().
diamonds |>
group_by(cut) |>
summarize(price = mean(price))
## # A tibble: 5 × 2
## cut price
## <ord> <dbl>
## 1 Fair 4359.
## 2 Good 3929.
## 3 Very Good 3982.
## 4 Premium 4584.
## 5 Ideal 3458.
tapply(diamonds$price, diamonds$cut, mean)
## Fair Good Very Good Premium Ideal
## 4358.758 3928.864 3981.760 4584.258 3457.542
Abschließend gibt es noch apply(), welches mit Matrizen
und Arrays arbeitet. Wir arbeiten aber häufiger mit Data
Frames. Mehr darüber ist aber online schnell zu finden.
for loopsfor Schleifen sind mächtig und wichtig und werden von
Fortgeschrittenen häufig angewendet. Die Struktur sieht wie folgt
aus:
for (element in vector) {
# do something with element
}
Ein einfaches Beispiel.
k <- numeric(20)
for (i in seq(1,20)) {
k[i] <- i*10
}
k
## [1] 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190
## [20] 200
Sie funktionieren ähnlich.
n = numeric(0)
p = 5
while(p < 10){
n = c(n, p)
p = p + 1
}
p
## [1] 10
n
## [1] 5 6 7 8 9
Kurz und schnell ohne Pakete.
# Left
hist(diamonds$carat)
# Right
plot(diamonds$carat, diamonds$price)